pi-smart-compact 7.12.5 → 7.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.13.0] - 2026-05-25
4
+
5
+ ### Added
6
+ - **Interactive metrics dashboard TUI** — `/smart-compact dashboard` now opens an in-terminal dashboard with overview, latest-run details, current-session runs, recent runs, and an explicit HTML export action.
7
+ - **Dashboard run detail formatting** — Added tested formatter helpers for legacy metrics, empty states, phase timings, compact run descriptions, and bounded percentage display.
8
+
9
+ ### Fixed
10
+ - **Provider cache percentage accounting** — Provider prompt-cache hit rates now use an effective prompt-token denominator and are capped at 100%, preventing impossible values such as `572610%` when providers report cached tokens separately from new input tokens.
11
+ - **Verification repair for malformed summaries** — Missing canonical sections are now reported as verification gaps and deterministic repair can create sections such as `## Goal`, `## Progress`, `## Critical Context`, and `## Files Modified` before injecting required facts.
12
+ - **Dashboard comparison noise** — Legacy metrics entries without profile/provider metadata are omitted from profile/provider comparison groups instead of appearing as misleading `unknown` rows.
13
+
14
+ ### Changed
15
+ - **Metrics readability** — Result overlays and notifications distinguish prompt, new, and cached input tokens when provider cache reads are present.
16
+ - **Dashboard navigation** — The TUI supports keybinding-aware navigation plus page-up/page-down and home/end scrolling.
17
+
3
18
  ## [7.12.5] - 2026-05-23
4
19
 
5
20
  ### Fixed
package/README.md CHANGED
@@ -186,7 +186,7 @@ The tool prepares a pending smart summary and lets Pi consume it on the next nat
186
186
  - pending summaries are kept in memory for **5 minutes**
187
187
  - exploration is adaptive and may be skipped for simple sessions
188
188
  - use `/smart-compact metrics` for profile/provider comparisons
189
- - use `/smart-compact dashboard` to write a local HTML report at `.cache/smart-compact-report.html`
189
+ - use `/smart-compact dashboard` to open the interactive TUI dashboard (overview, latest run, current session, recent runs, or write HTML)
190
190
 
191
191
  This keeps the extension helpful without forcing extra work when it is not needed.
192
192
 
@@ -2,7 +2,7 @@
2
2
  * Constants, prompts, and profile defaults.
3
3
  */
4
4
  import type { CompressionProfile, ProfileConfig } from "./types.ts";
5
- export declare const VERSION = "7.12.5";
5
+ export declare const VERSION = "7.13.0";
6
6
  export declare const CHARS_PER_TOKEN = 3.8;
7
7
  export declare const COMPACT_SYSTEM_PREFIX: string;
8
8
  export declare const PROFILES: Record<CompressionProfile, ProfileConfig>;
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE/E,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EACV,kBAAkB,EAAE,iBAAiB,EAEtC,MAAM,YAAY,CAAC;AAwBpB,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,uBAAuB,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE;QAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,SAAS,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiiB9E"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE/E,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EACV,kBAAkB,EAAE,iBAAiB,EAEtC,MAAM,YAAY,CAAC;AAwBpB,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,uBAAuB,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE;QAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,SAAS,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqiB9E"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,iCAAiC,CAAC;AA0C7F,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAAC,EAAE,EAAE,YAAY,QA6L7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,iCAAiC,CAAC;AA0C7F,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAAC,EAAE,EAAE,YAAY,QAwM7D"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // src/constants.ts
3
- var VERSION = "7.12.5";
3
+ var VERSION = "7.13.0";
4
4
  var CHARS_PER_TOKEN = 3.8;
5
5
  var COMPACT_SYSTEM_PREFIX = "You are an expert conversation summarizer for a coding agent. " + "Produce structured markdown summaries. " + "Follow output format exactly. " + "Use EXACT names \u2014 never paraphrase code identifiers. " + "Trust deterministic extraction data over intuition.";
6
6
  var PROFILES = {
@@ -854,6 +854,11 @@ function recordMetric(m) {
854
854
  if (_metrics.length > 200)
855
855
  _metrics.splice(0, _metrics.length - 100);
856
856
  }
857
+ function effectivePromptInputTokens(inputTokens, cacheHitTokens) {
858
+ if (cacheHitTokens <= 0)
859
+ return Math.max(0, inputTokens);
860
+ return cacheHitTokens > inputTokens ? inputTokens + cacheHitTokens : inputTokens;
861
+ }
857
862
  function getMetricsSummary() {
858
863
  const n = _metrics.length;
859
864
  if (!n)
@@ -862,13 +867,14 @@ function getMetricsSummary() {
862
867
  const totalOutput = _metrics.reduce((s, m) => s + m.outputTokens, 0);
863
868
  const totalCacheHit = _metrics.reduce((s, m) => s + m.cacheHitTokens, 0);
864
869
  const avgLatency = _metrics.reduce((s, m) => s + m.latencyMs, 0) / n;
870
+ const cacheDenominator = effectivePromptInputTokens(totalInput, totalCacheHit);
865
871
  return {
866
872
  totalCalls: n,
867
873
  totalInput,
868
874
  totalOutput,
869
875
  totalCacheHit,
870
876
  avgLatency: Math.round(avgLatency),
871
- cacheHitRate: totalInput > 0 ? totalCacheHit / totalInput : 0
877
+ cacheHitRate: cacheDenominator > 0 ? Math.min(1, totalCacheHit / cacheDenominator) : 0
872
878
  };
873
879
  }
874
880
  async function trackedComplete(phase, model, reqBody, opts) {
@@ -1104,9 +1110,9 @@ function summarizeDashboard(entries) {
1104
1110
  successRate: entries.length ? success / entries.length : 0,
1105
1111
  avgDuration: Math.round(average(durations)),
1106
1112
  p95Duration: percentile(durations, 95),
1107
- totalCalls: entries.reduce((sum, e) => sum + e.totalCalls, 0),
1108
- totalInput: entries.reduce((sum, e) => sum + e.totalInput, 0),
1109
- totalOutput: entries.reduce((sum, e) => sum + e.totalOutput, 0),
1113
+ totalCalls: entries.reduce((sum, e) => sum + (e.totalCalls ?? 0), 0),
1114
+ totalInput: entries.reduce((sum, e) => sum + (e.totalInput ?? 0), 0),
1115
+ totalOutput: entries.reduce((sum, e) => sum + (e.totalOutput ?? 0), 0),
1110
1116
  totalSaved: entries.reduce((sum, e) => sum + (e.tokensSaved ?? 0), 0),
1111
1117
  avgScore: Math.round(average(scored))
1112
1118
  };
@@ -1114,7 +1120,9 @@ function summarizeDashboard(entries) {
1114
1120
  function groupMetrics(entries, keyFn) {
1115
1121
  const groups = new Map;
1116
1122
  for (const entry of entries) {
1117
- const key = keyFn(entry) || "unknown";
1123
+ const key = keyFn(entry);
1124
+ if (!key)
1125
+ continue;
1118
1126
  groups.set(key, [...groups.get(key) ?? [], entry]);
1119
1127
  }
1120
1128
  return [...groups.entries()].map(([name, group]) => {
@@ -1128,7 +1136,7 @@ function groupMetrics(entries, keyFn) {
1128
1136
  p95Duration: percentile(durations, 95),
1129
1137
  avgScore: Math.round(average(scores)),
1130
1138
  totalSaved: group.reduce((sum, e) => sum + (e.tokensSaved ?? 0), 0),
1131
- totalCalls: group.reduce((sum, e) => sum + e.totalCalls, 0),
1139
+ totalCalls: group.reduce((sum, e) => sum + (e.totalCalls ?? 0), 0),
1132
1140
  errorRate: group.length ? failures / group.length : 0
1133
1141
  };
1134
1142
  }).sort((a, b) => b.runs - a.runs || a.name.localeCompare(b.name));
@@ -1197,7 +1205,7 @@ function recentRunRows(entries) {
1197
1205
  <td class="num">${escapeHtml(formatMs(durationOf(entry)))}</td>
1198
1206
  <td class="num">${typeof entry.verificationScore === "number" ? formatNumber(entry.verificationScore) : "\u2014"}</td>
1199
1207
  <td class="num">${typeof entry.tokensSaved === "number" ? formatNumber(entry.tokensSaved) : "\u2014"}</td>
1200
- <td class="num">${formatNumber(entry.totalCalls)}</td>
1208
+ <td class="num">${formatNumber(entry.totalCalls ?? 0)}</td>
1201
1209
  <td class="num">${typeof entry.extractionCacheHitRate === "number" ? formatPercent(entry.extractionCacheHitRate) : "\u2014"}</td>
1202
1210
  <td class="mono small reason">${escapeHtml(entry.fallbackReason ?? entry.extractionCacheMissReason ?? "")}</td>
1203
1211
  </tr>`).join(`
@@ -1210,8 +1218,8 @@ function buildMetricsReport(entries = readMetricsLog(100)) {
1210
1218
  if (!entries.length)
1211
1219
  return "No smart-compact metrics recorded yet.";
1212
1220
  const summary = summarizeDashboard(entries);
1213
- const byProfile = groupMetrics(entries, (e) => e.profile ?? "unknown");
1214
- const byProvider = groupMetrics(entries, (e) => e.provider ?? e.model?.split("/")[0] ?? "unknown");
1221
+ const byProfile = groupMetrics(entries, (e) => e.profile);
1222
+ const byProvider = groupMetrics(entries, (e) => e.provider ?? e.model?.split("/")[0]);
1215
1223
  const summarizeGroup = (group) => "- " + group.name + ": n=" + group.runs + ", avg=" + group.avgDuration + "ms, p95=" + group.p95Duration + "ms, score=" + group.avgScore + ", saved=" + group.totalSaved + "t, reliability=" + formatPercent(1 - group.errorRate);
1216
1224
  const extractionCacheRuns = entries.filter((e) => typeof e.extractionCacheHitRate === "number");
1217
1225
  const extractionCacheAvg = average(extractionCacheRuns.map((e) => e.extractionCacheHitRate ?? 0));
@@ -1240,8 +1248,8 @@ function writeMetricsDashboard(entries = readMetricsLog(200)) {
1240
1248
  const summary = summarizeDashboard(entries);
1241
1249
  const latest = entries[entries.length - 1];
1242
1250
  const report = buildMetricsReport(entries);
1243
- const profileGroups = groupMetrics(entries, (e) => e.profile ?? "unknown");
1244
- const providerGroups = groupMetrics(entries, (e) => e.provider ?? e.model?.split("/")[0] ?? "unknown");
1251
+ const profileGroups = groupMetrics(entries, (e) => e.profile);
1252
+ const providerGroups = groupMetrics(entries, (e) => e.provider ?? e.model?.split("/")[0]);
1245
1253
  const healthTone = summary.error + summary.timeout > 0 ? "warn" : "good";
1246
1254
  const html = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Smart Compact Metrics</title><style>${dashboardCss()}</style></head><body><main>
1247
1255
  <header><div><div class="eyebrow">pi-smart-compact</div><h1>Operational Metrics</h1><div class="muted">Generated ${escapeHtml(new Date().toISOString())} \xB7 ${formatNumber(entries.length)} recent runs \xB7 local file dashboard</div></div><div>${badge(latest?.status)} ${latest ? `<span class="muted">latest ${escapeHtml(latest.profile ?? "unknown")}</span>` : ""}</div></header>
@@ -3154,12 +3162,18 @@ function verifySummary(summary, extraction) {
3154
3162
  score -= 10;
3155
3163
  }
3156
3164
  }
3157
- if (!lower.includes("## goal"))
3165
+ if (!lower.includes("## goal")) {
3166
+ gaps.push("Missing section: ## Goal");
3158
3167
  score -= 5;
3159
- if (!lower.includes("## progress"))
3168
+ }
3169
+ if (!lower.includes("## progress")) {
3170
+ gaps.push("Missing section: ## Progress");
3160
3171
  score -= 5;
3161
- if (!lower.includes("## critical context"))
3172
+ }
3173
+ if (!lower.includes("## critical context")) {
3174
+ gaps.push("Missing section: ## Critical Context");
3162
3175
  score -= 3;
3176
+ }
3163
3177
  const summaryFileRefs = (summary.match(/[\w.\/-]+\.[\w]+/g) ?? []).filter((p) => p.includes("/") || p.match(/\.(ts|tsx|js|jsx|rs|py|go|java|rb|css|html|json|yaml|yml|toml|md|sh|sql)$/i));
3164
3178
  const knownFiles = new Set([
3165
3179
  ...extraction.modifiedFiles.map((f) => f.path.toLowerCase()),
@@ -3209,7 +3223,8 @@ function verifySummary(summary, extraction) {
3209
3223
  score -= 5;
3210
3224
  }
3211
3225
  }
3212
- return { ok: gaps.length === 0, gaps, score: Math.max(0, score) };
3226
+ const finalScore = Math.max(0, score);
3227
+ return { ok: gaps.length === 0 && finalScore >= 85, gaps, score: finalScore };
3213
3228
  }
3214
3229
  function patchDeterministic(summary, gaps, extraction) {
3215
3230
  let patched = summary;
@@ -3217,47 +3232,75 @@ function patchDeterministic(summary, gaps, extraction) {
3217
3232
  const errorGaps = gaps.filter((g) => g.startsWith("Missing error:"));
3218
3233
  const constraintGaps = gaps.filter((g) => g.startsWith("Missing constraint:"));
3219
3234
  const decisionGaps = gaps.filter((g) => g.startsWith("Missing decision:"));
3220
- const otherGaps = gaps.filter((g) => !g.startsWith("Missing modified file:") && !g.startsWith("Missing error:") && !g.startsWith("Missing constraint:") && !g.startsWith("Missing decision:") && !g.startsWith("Potentially fabricated") && !g.startsWith("Inconsistency"));
3221
- const findSectionInsert = (header) => {
3222
- const re = new RegExp(header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*\\n", "i");
3235
+ const sectionGaps = gaps.filter((g) => g.startsWith("Missing section:"));
3236
+ const otherGaps = gaps.filter((g) => !g.startsWith("Missing modified file:") && !g.startsWith("Missing error:") && !g.startsWith("Missing constraint:") && !g.startsWith("Missing decision:") && !g.startsWith("Missing section:") && !g.startsWith("Potentially fabricated") && !g.startsWith("Inconsistency"));
3237
+ const escapeRe = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3238
+ const findOrCreateSectionInsert = (header, defaultBody = "") => {
3239
+ const re = new RegExp(escapeRe(header) + "\\s*\\n", "i");
3223
3240
  const m = patched.match(re);
3224
- return m?.index != null ? m.index + m[0].length : null;
3241
+ if (m?.index != null)
3242
+ return m.index + m[0].length;
3243
+ const prefix = patched.endsWith(`
3244
+ `) ? patched.endsWith(`
3245
+
3246
+ `) ? "" : `
3247
+ ` : `
3248
+
3249
+ `;
3250
+ patched += prefix + header + `
3251
+ ` + (defaultBody ? defaultBody.replace(/\n?$/, `
3252
+ `) : "");
3253
+ return patched.length;
3254
+ };
3255
+ const ensureMissingSection = (header) => {
3256
+ if (header === "## Goal") {
3257
+ findOrCreateSectionInsert(header, (extraction.mainGoal ?? "Continue the current coding task.") + `
3258
+ `);
3259
+ } else if (header === "## Progress") {
3260
+ findOrCreateSectionInsert(header, `### Done
3261
+ - See preceding summary.
3262
+ ### In Progress
3263
+ - Continue from the latest user request.
3264
+ ### Blocked
3265
+ - None recorded.
3266
+ `);
3267
+ } else if (header === "## Critical Context") {
3268
+ findOrCreateSectionInsert(header, `- None recorded.
3269
+ `);
3270
+ } else {
3271
+ findOrCreateSectionInsert(header);
3272
+ }
3225
3273
  };
3274
+ for (const gap of sectionGaps) {
3275
+ ensureMissingSection(gap.replace("Missing section: ", ""));
3276
+ }
3226
3277
  if (fileGaps.length > 0) {
3227
- const insertPos = findSectionInsert("## Files Modified");
3228
- if (insertPos != null) {
3229
- const entries = fileGaps.map((g) => "- " + g.replace("Missing modified file: ", "")).join(`
3278
+ const insertPos = findOrCreateSectionInsert("## Files Modified");
3279
+ const entries = fileGaps.map((g) => "- " + g.replace("Missing modified file: ", "")).join(`
3230
3280
  `) + `
3231
3281
  `;
3232
- patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3233
- }
3282
+ patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3234
3283
  }
3235
3284
  if (errorGaps.length > 0) {
3236
- const insertPos = findSectionInsert("## Critical Context");
3237
- if (insertPos != null) {
3238
- const entries = errorGaps.map((g) => "- " + g).join(`
3285
+ const insertPos = findOrCreateSectionInsert("## Critical Context");
3286
+ const entries = errorGaps.map((g) => "- " + g).join(`
3239
3287
  `) + `
3240
3288
  `;
3241
- patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3242
- }
3289
+ patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3243
3290
  }
3244
3291
  if (constraintGaps.length > 0) {
3245
- const insertPos = findSectionInsert("## Constraints & Preferences");
3246
- if (insertPos != null) {
3247
- const entries = constraintGaps.map((g) => "- " + g).join(`
3292
+ const insertPos = findOrCreateSectionInsert("## Constraints & Preferences");
3293
+ const entries = constraintGaps.map((g) => "- " + g).join(`
3248
3294
  `) + `
3249
3295
  `;
3250
- patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3251
- }
3296
+ patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3252
3297
  }
3253
3298
  if (decisionGaps.length > 0) {
3254
- const insertPos = findSectionInsert("## Key Decisions");
3255
- if (insertPos != null) {
3256
- const entries = decisionGaps.map((g) => "- **" + g.replace("Missing decision: ", "") + "**").join(`
3299
+ const insertPos = findOrCreateSectionInsert("## Key Decisions");
3300
+ const entries = decisionGaps.map((g) => "- **" + g.replace("Missing decision: ", "") + "**").join(`
3257
3301
  `) + `
3258
3302
  `;
3259
- patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3260
- }
3303
+ patched = patched.slice(0, insertPos) + entries + patched.slice(insertPos);
3261
3304
  }
3262
3305
  if (otherGaps.length > 0) {
3263
3306
  patched += `
@@ -3294,7 +3337,106 @@ Return the COMPLETE updated summary with missing items integrated. Keep the same
3294
3337
 
3295
3338
  // src/ui/overlays.ts
3296
3339
  import { DynamicBorder } from "@earendil-works/pi-coding-agent";
3297
- import { Container, SelectList, Text } from "@earendil-works/pi-tui";
3340
+ import { Container, Key, matchesKey, SelectList, Text, truncateToWidth } from "@earendil-works/pi-tui";
3341
+
3342
+ // src/ui/dashboard-format.ts
3343
+ var DASHBOARD_PAGE_SIZE = 24;
3344
+ function metricDuration(entry) {
3345
+ return entry.durationMs ?? entry.phaseTimings?.reduce((sum, phase) => sum + phase.durationMs, 0) ?? 0;
3346
+ }
3347
+ function metricMs(ms) {
3348
+ if (!Number.isFinite(ms) || ms <= 0)
3349
+ return "0ms";
3350
+ return ms >= 1000 ? (ms / 1000).toFixed(ms >= 1e4 ? 0 : 1) + "s" : Math.round(ms) + "ms";
3351
+ }
3352
+ function clampRatio(value) {
3353
+ return Math.max(0, Math.min(1, value));
3354
+ }
3355
+ function metricPct(value) {
3356
+ return typeof value === "number" && Number.isFinite(value) ? Math.round(clampRatio(value) * 100) + "%" : "\u2014";
3357
+ }
3358
+ function metricNum(value) {
3359
+ return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString() : "\u2014";
3360
+ }
3361
+ function metricScore(entry) {
3362
+ return typeof entry?.verificationScore === "number" && Number.isFinite(entry.verificationScore) ? entry.verificationScore + "/100" : "\u2014";
3363
+ }
3364
+ function formatMetricRun(entry, index) {
3365
+ const prefix = typeof index === "number" ? String(index).padStart(2, " ") + ". " : "";
3366
+ const time = entry.ts ? new Date(entry.ts).toLocaleString() : "unknown time";
3367
+ return prefix + time + " | " + (entry.profile ?? "?") + " | " + (entry.provider ?? entry.model ?? "?") + " | " + (entry.method ?? "?") + " | " + (entry.status ?? "?") + " | score " + metricScore(entry) + " | saved " + metricNum(entry.tokensSaved) + "t";
3368
+ }
3369
+ function formatMetricRunCompact(entry) {
3370
+ return "score " + metricScore(entry) + " \u2022 saved " + metricNum(entry.tokensSaved) + "t \u2022 " + (entry.status ?? "?") + " \u2022 " + (entry.profile ?? "?") + " / " + (entry.provider ?? entry.model ?? "?");
3371
+ }
3372
+ function formatPhaseTiming(phase, total) {
3373
+ const share = total > 0 ? Math.round(phase.durationMs / total * 100) : 0;
3374
+ return "- " + phase.phase + ": " + metricMs(phase.durationMs) + " (" + share + "%)";
3375
+ }
3376
+ function formatRunDetails(entry, title) {
3377
+ if (!entry)
3378
+ return [title, "", "No run recorded yet."];
3379
+ const totalDuration = metricDuration(entry);
3380
+ const lines = [
3381
+ title,
3382
+ "",
3383
+ "Session: " + entry.sessionId,
3384
+ "Time: " + (entry.ts ? new Date(entry.ts).toLocaleString() : "unknown"),
3385
+ "Status: " + (entry.status ?? "unknown") + " | run: " + (entry.runType ?? "?") + " | profile: " + (entry.profile ?? "?"),
3386
+ "Provider/model: " + (entry.provider ?? "?") + " / " + (entry.model ?? "?"),
3387
+ "Method: " + (entry.method ?? "?") + " | duration: " + metricMs(totalDuration),
3388
+ "Quality: " + metricScore(entry) + " | gaps: " + metricNum(entry.verificationGaps),
3389
+ "Tokens: before " + metricNum(entry.tokensBefore) + "t | saved " + metricNum(entry.tokensSaved) + "t | prune saved " + metricNum(entry.pruneSavedTokens) + "t",
3390
+ "LLM: " + metricNum(entry.totalCalls) + " calls | input " + metricNum(entry.totalInput) + "t | output " + metricNum(entry.totalOutput) + "t | provider cache " + metricPct(entry.cacheHitRate),
3391
+ "Extraction cache: " + metricNum(entry.extractionCacheHits) + " hit / " + metricNum(entry.extractionCacheMisses) + " miss | rate " + metricPct(entry.extractionCacheHitRate),
3392
+ "Context: " + metricNum(entry.contextPercent) + "% | tool share: " + metricNum(entry.toolPercent) + "% | chunks: " + metricNum(entry.chunkCount)
3393
+ ];
3394
+ if (entry.extractionCacheMissReason)
3395
+ lines.push("Extraction miss reason: " + entry.extractionCacheMissReason);
3396
+ if (entry.fallbackReason)
3397
+ lines.push("Reason: " + entry.fallbackReason);
3398
+ if (entry.phaseTimings?.length) {
3399
+ lines.push("", "Phase timings:");
3400
+ lines.push(...entry.phaseTimings.map((phase) => formatPhaseTiming(phase, totalDuration)));
3401
+ }
3402
+ return lines;
3403
+ }
3404
+ function formatCurrentSession(entries, currentSessionId) {
3405
+ if (!currentSessionId || currentSessionId === "unknown")
3406
+ return ["Current session", "", "Session id is not available from Pi context."];
3407
+ const runs = entries.filter((entry) => entry.sessionId === currentSessionId);
3408
+ if (!runs.length)
3409
+ return ["Current session", "", "Session: " + currentSessionId, "No smart-compact metrics recorded for this session yet."];
3410
+ const success = runs.filter((entry) => entry.status === "success").length;
3411
+ const latest = runs[runs.length - 1];
3412
+ const totalSaved = runs.reduce((sum, entry) => sum + (entry.tokensSaved ?? 0), 0);
3413
+ const avgScoreValues = runs.map((entry) => entry.verificationScore).filter((v) => typeof v === "number" && Number.isFinite(v));
3414
+ const avgScore = avgScoreValues.length ? Math.round(avgScoreValues.reduce((sum, value) => sum + value, 0) / avgScoreValues.length) : 0;
3415
+ return [
3416
+ "Current session",
3417
+ "",
3418
+ "Session: " + currentSessionId,
3419
+ "Runs: " + runs.length + " | success " + success + " | total saved " + totalSaved.toLocaleString() + "t | avg score " + (avgScore || "\u2014"),
3420
+ "Latest: " + formatMetricRun(latest),
3421
+ "",
3422
+ "Runs in this session:",
3423
+ ...runs.slice(-20).reverse().map((entry, i) => formatMetricRun(entry, i + 1))
3424
+ ];
3425
+ }
3426
+ function formatRecentRuns(entries) {
3427
+ if (!entries.length)
3428
+ return ["Recent runs", "", "No smart-compact metrics recorded yet."];
3429
+ return [
3430
+ "Recent runs",
3431
+ "",
3432
+ ...entries.slice(-30).reverse().map((entry, i) => formatMetricRun(entry, i + 1))
3433
+ ];
3434
+ }
3435
+ function isDashboardTitleLine(line) {
3436
+ return line.startsWith("#") || line === "Latest run details" || line === "Current session" || line === "Recent runs" || line === "Phase timings:" || line === "Runs in this session:";
3437
+ }
3438
+
3439
+ // src/ui/overlays.ts
3298
3440
  import path8 from "path";
3299
3441
  function renderContextBar(theme, pct, tokens, barLen = 24) {
3300
3442
  const clamped = Math.min(Math.max(pct, 0), 100);
@@ -3443,8 +3585,10 @@ async function showResultScreen(ctx, details, extraction) {
3443
3585
  if (ms.totalCalls > 0) {
3444
3586
  const providerCachePct = Math.round(ms.cacheHitRate * 100);
3445
3587
  const extractionCachePct = Math.round(ecs.hitRate * 100);
3588
+ const promptInput = effectivePromptInputTokens(ms.totalInput, ms.totalCacheHit);
3589
+ const inputLabel = ms.totalCacheHit > 0 ? promptInput.toLocaleString() + "t prompt (" + ms.totalInput.toLocaleString() + "t new, " + ms.totalCacheHit.toLocaleString() + "t cached)" : ms.totalInput.toLocaleString() + "t in";
3446
3590
  const cacheColor = extractionCachePct >= 50 ? "success" : extractionCachePct >= 20 ? "warning" : "dim";
3447
- c.addChild(new Text(theme.fg("dim", " LLM: ") + theme.fg("text", ms.totalCalls + " calls") + theme.fg("dim", " \u2022 ") + theme.fg("text", ms.totalInput.toLocaleString() + "t in") + theme.fg("dim", " \u2022 ") + theme.fg("dim", providerCachePct + "% provider cache") + theme.fg("dim", " \u2022 ") + theme.fg(cacheColor, extractionCachePct + "% extraction cache") + theme.fg("dim", " \u2022 ") + theme.fg("dim", ms.avgLatency + "ms avg"), 0, 0));
3591
+ c.addChild(new Text(theme.fg("dim", " LLM: ") + theme.fg("text", ms.totalCalls + " calls") + theme.fg("dim", " \u2022 ") + theme.fg("text", inputLabel) + theme.fg("dim", " \u2022 ") + theme.fg("dim", providerCachePct + "% provider cache") + theme.fg("dim", " \u2022 ") + theme.fg(cacheColor, extractionCachePct + "% extraction cache") + theme.fg("dim", " \u2022 ") + theme.fg("dim", ms.avgLatency + "ms avg"), 0, 0));
3448
3592
  }
3449
3593
  const modFiles = details.modifiedFiles;
3450
3594
  const errCount = extraction.errors.length;
@@ -3524,6 +3668,116 @@ async function showResultScreen(ctx, details, extraction) {
3524
3668
  };
3525
3669
  }, { overlay: true, overlayOptions: { width: "70%", anchor: "center", maxHeight: "80%" } });
3526
3670
  }
3671
+ async function showMetricsDashboardUI(ctx, opts) {
3672
+ const entries = opts.entries;
3673
+ const latest = entries[entries.length - 1];
3674
+ const currentRuns = opts.currentSessionId ? entries.filter((entry) => entry.sessionId === opts.currentSessionId) : [];
3675
+ const menuItems = [
3676
+ { view: "overview", label: "Overview report", desc: entries.length + " recent run(s), profile/provider comparison" },
3677
+ { view: "latest", label: "Latest run details", desc: latest ? formatMetricRunCompact(latest) : "No run recorded yet" },
3678
+ { view: "session", label: "Current session", desc: (opts.currentSessionId ?? "unknown") + " \u2014 " + currentRuns.length + " run(s)" },
3679
+ { view: "recent", label: "Recent runs", desc: "Last " + Math.min(entries.length, 30) + " run(s)" },
3680
+ { action: "html", label: "Write HTML dashboard", desc: "Generate ~/.pi/agent/.cache/smart-compact-report.html" }
3681
+ ];
3682
+ return await ctx.ui.custom((tui, theme, keybindings, done) => {
3683
+ let view = "menu";
3684
+ let selected = 0;
3685
+ let scroll = 0;
3686
+ const pageLines = () => {
3687
+ if (view === "overview")
3688
+ return opts.report.split(`
3689
+ `);
3690
+ if (view === "latest")
3691
+ return formatRunDetails(latest, "Latest run details");
3692
+ if (view === "session")
3693
+ return formatCurrentSession(entries, opts.currentSessionId);
3694
+ if (view === "recent")
3695
+ return formatRecentRuns(entries);
3696
+ return [];
3697
+ };
3698
+ const resetPage = (nextView) => {
3699
+ view = nextView;
3700
+ scroll = 0;
3701
+ };
3702
+ const renderHeader = (width) => [
3703
+ truncateToWidth(theme.fg("accent", theme.bold(" \uD83D\uDCCA Smart Compact Dashboard")) + theme.fg("dim", " " + entries.length + " recorded run(s)"), width),
3704
+ truncateToWidth(theme.fg("dim", " session: " + (opts.currentSessionId ?? "unknown")) + theme.fg("dim", latest ? " \u2022 latest score " + metricScore(latest) : ""), width),
3705
+ truncateToWidth(theme.fg("borderMuted", "\u2500".repeat(Math.max(0, width))), width)
3706
+ ];
3707
+ return {
3708
+ render: (width) => {
3709
+ const lines = renderHeader(width);
3710
+ if (view === "menu") {
3711
+ lines.push(truncateToWidth(theme.fg("text", " Choose what to inspect:"), width), "");
3712
+ for (let i = 0;i < menuItems.length; i++) {
3713
+ const item = menuItems[i];
3714
+ const active = i === selected;
3715
+ const prefix = active ? " \u203A " : " ";
3716
+ const label = active ? theme.fg("accent", theme.bold(item.label)) : theme.fg("text", item.label);
3717
+ lines.push(truncateToWidth(prefix + label, width));
3718
+ lines.push(truncateToWidth(" " + theme.fg(active ? "muted" : "dim", item.desc), width));
3719
+ }
3720
+ lines.push("", truncateToWidth(theme.fg("dim", " \u2191\u2193 navigate \u2022 enter open \u2022 esc/q close"), width));
3721
+ return lines;
3722
+ }
3723
+ const content = pageLines();
3724
+ const available = DASHBOARD_PAGE_SIZE;
3725
+ const maxScroll = Math.max(0, content.length - available);
3726
+ if (scroll > maxScroll)
3727
+ scroll = maxScroll;
3728
+ for (const line of content.slice(scroll, scroll + available)) {
3729
+ const styled = isDashboardTitleLine(line) ? theme.fg("accent", theme.bold(line)) : line.startsWith("-") ? theme.fg("dim", line) : theme.fg("text", line);
3730
+ lines.push(truncateToWidth(" " + styled, width));
3731
+ }
3732
+ if (content.length > available) {
3733
+ lines.push(truncateToWidth(theme.fg("dim", " showing " + (scroll + 1) + "-" + Math.min(content.length, scroll + available) + " of " + content.length), width));
3734
+ }
3735
+ lines.push("", truncateToWidth(theme.fg("dim", " \u2191\u2193 scroll \u2022 pgup/pgdn page \u2022 home/end jump \u2022 b back \u2022 esc/q close"), width));
3736
+ return lines;
3737
+ },
3738
+ invalidate: () => {},
3739
+ handleInput: (data) => {
3740
+ if (keybindings.matches(data, "tui.select.cancel") || data === "q") {
3741
+ done(null);
3742
+ return;
3743
+ }
3744
+ if (view === "menu") {
3745
+ if (keybindings.matches(data, "tui.select.up"))
3746
+ selected = Math.max(0, selected - 1);
3747
+ else if (keybindings.matches(data, "tui.select.down"))
3748
+ selected = Math.min(menuItems.length - 1, selected + 1);
3749
+ else if (keybindings.matches(data, "tui.select.confirm")) {
3750
+ const item = menuItems[selected];
3751
+ if (item.action) {
3752
+ done(item.action);
3753
+ return;
3754
+ }
3755
+ if (item.view)
3756
+ resetPage(item.view);
3757
+ }
3758
+ } else {
3759
+ const content = pageLines();
3760
+ const maxScroll = Math.max(0, content.length - DASHBOARD_PAGE_SIZE);
3761
+ if (data === "b" || matchesKey(data, Key.left))
3762
+ resetPage("menu");
3763
+ else if (matchesKey(data, Key.home))
3764
+ scroll = 0;
3765
+ else if (matchesKey(data, Key.end))
3766
+ scroll = maxScroll;
3767
+ else if (keybindings.matches(data, "tui.select.pageUp"))
3768
+ scroll = Math.max(0, scroll - DASHBOARD_PAGE_SIZE);
3769
+ else if (keybindings.matches(data, "tui.select.pageDown"))
3770
+ scroll = Math.min(maxScroll, scroll + DASHBOARD_PAGE_SIZE);
3771
+ else if (keybindings.matches(data, "tui.select.up"))
3772
+ scroll = Math.max(0, scroll - 1);
3773
+ else if (keybindings.matches(data, "tui.select.down"))
3774
+ scroll = Math.min(maxScroll, scroll + 1);
3775
+ }
3776
+ tui.requestRender();
3777
+ }
3778
+ };
3779
+ }, { overlay: true, overlayOptions: { width: "80%", anchor: "center", maxHeight: "85%" } });
3780
+ }
3527
3781
  async function showCompactUI(ctx, opts) {
3528
3782
  const selectedModel = await selectModel(ctx, opts);
3529
3783
  if (!selectedModel)
@@ -4007,7 +4261,9 @@ async function runSmartCompact(opts) {
4007
4261
  if (ms.totalCalls > 0) {
4008
4262
  const providerCacheRate = Math.round(ms.cacheHitRate * 100);
4009
4263
  const extractionCacheRate = Math.round(ecs.hitRate * 100);
4010
- notify("Metrics: " + ms.totalCalls + " calls, " + ms.totalInput + "t in, " + ms.totalOutput + "t out, provider-cache " + providerCacheRate + "% (internal phases disabled), extraction-cache " + extractionCacheRate + "%, " + ms.avgLatency + "ms avg", "info");
4264
+ const promptInput = effectivePromptInputTokens(ms.totalInput, ms.totalCacheHit);
4265
+ const inputLabel = ms.totalCacheHit > 0 ? promptInput + "t prompt (" + ms.totalInput + "t new, " + ms.totalCacheHit + "t cached)" : ms.totalInput + "t in";
4266
+ notify("Metrics: " + ms.totalCalls + " calls, " + inputLabel + ", " + ms.totalOutput + "t out, provider-cache " + providerCacheRate + "% (internal phases disabled), extraction-cache " + extractionCacheRate + "%, " + ms.avgLatency + "ms avg", "info");
4011
4267
  }
4012
4268
  if (!autoTriggered) {
4013
4269
  try {
@@ -4106,12 +4362,21 @@ function smartCompactExtension(pi) {
4106
4362
  const verbose = flags.includes("verbose") || flags.includes("debug");
4107
4363
  const dryRun = flags.includes("dry-run");
4108
4364
  if (flags.includes("metrics") || flags.includes("dashboard")) {
4109
- const dashboard = flags.includes("dashboard");
4110
- const report = buildMetricsReport();
4111
- const fp = dashboard ? writeMetricsDashboard() : null;
4112
- ctx.ui.notify(report + (fp ? `
4113
-
4114
- Dashboard: ` + fp : ""), "info");
4365
+ if (flags.includes("dashboard")) {
4366
+ const entries = readMetricsLog(200);
4367
+ const sessionId = ctx.sessionManager.getSessionId?.() ?? "unknown";
4368
+ const action = await showMetricsDashboardUI(ctx, {
4369
+ entries,
4370
+ currentSessionId: sessionId,
4371
+ report: buildMetricsReport(entries)
4372
+ });
4373
+ if (action === "html") {
4374
+ const fp = writeMetricsDashboard(entries);
4375
+ ctx.ui.notify(fp ? "Dashboard written: " + fp : "Dashboard could not be written", fp ? "info" : "error");
4376
+ }
4377
+ } else {
4378
+ ctx.ui.notify(buildMetricsReport(), "info");
4379
+ }
4115
4380
  return;
4116
4381
  }
4117
4382
  const modelArg = tokens.find((t) => t.includes("/"));
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/phases/verify.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAqB,MAAM,aAAa,CAAC;AAK/F,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB,GAAG,kBAAkB,CAwGnG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,oBAAoB,GAAG,MAAM,CAgE5G;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAClG,OAAO,CAAC,MAAM,CAAC,CAcjB"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/phases/verify.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAqB,MAAM,aAAa,CAAC;AAK/F,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB,GAAG,kBAAkB,CAyGnG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,oBAAoB,GAAG,MAAM,CAiF5G;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAClG,OAAO,CAAC,MAAM,CAAC,CAcjB"}
@@ -0,0 +1,14 @@
1
+ import type { CompactMetricsEntry } from "../types.ts";
2
+ export declare const DASHBOARD_PAGE_SIZE = 24;
3
+ export declare function metricDuration(entry: CompactMetricsEntry): number;
4
+ export declare function metricMs(ms: number): string;
5
+ export declare function metricPct(value: number | undefined): string;
6
+ export declare function metricNum(value: number | undefined): string;
7
+ export declare function metricScore(entry: CompactMetricsEntry | undefined): string;
8
+ export declare function formatMetricRun(entry: CompactMetricsEntry, index?: number): string;
9
+ export declare function formatMetricRunCompact(entry: CompactMetricsEntry): string;
10
+ export declare function formatRunDetails(entry: CompactMetricsEntry | undefined, title: string): string[];
11
+ export declare function formatCurrentSession(entries: CompactMetricsEntry[], currentSessionId: string | undefined): string[];
12
+ export declare function formatRecentRuns(entries: CompactMetricsEntry[]): string[];
13
+ export declare function isDashboardTitleLine(line: string): boolean;
14
+ //# sourceMappingURL=dashboard-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-format.d.ts","sourceRoot":"","sources":["../../src/ui/dashboard-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,aAAa,CAAC;AAE5E,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAEjE;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAG3C;AAMD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE3D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,CAE1E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAEzE;AAOD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBhG;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,gBAAgB,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAmBnH;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAOzE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D"}
@@ -2,7 +2,7 @@
2
2
  * TUI overlays: model/profile selection, progress, result screen.
3
3
  */
4
4
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
5
- import type { CompressionProfile, ModelOption, ProgressState, SmartCompactDetails, StructuredExtraction } from "../types.ts";
5
+ import type { CompactMetricsEntry, CompressionProfile, ModelOption, ProgressState, SmartCompactDetails, StructuredExtraction } from "../types.ts";
6
6
  export declare function renderContextBar(theme: any, pct: number, tokens: number, barLen?: number): string;
7
7
  export declare function renderTokenBar(theme: any, before: number, after: number, label: string, barLen?: number): string;
8
8
  export declare function selectModel(ctx: ExtensionCommandContext, opts: {
@@ -17,6 +17,12 @@ export declare function selectProfile(ctx: ExtensionCommandContext, selectedMode
17
17
  }): Promise<CompressionProfile | null>;
18
18
  export declare function showProgressOverlay(ctx: ExtensionCommandContext, state: ProgressState): void;
19
19
  export declare function showResultScreen(ctx: ExtensionCommandContext, details: SmartCompactDetails, extraction: StructuredExtraction): Promise<void>;
20
+ type DashboardAction = "html" | null;
21
+ export declare function showMetricsDashboardUI(ctx: ExtensionCommandContext, opts: {
22
+ entries: CompactMetricsEntry[];
23
+ currentSessionId?: string;
24
+ report: string;
25
+ }): Promise<DashboardAction>;
20
26
  export declare function showCompactUI(ctx: ExtensionCommandContext, opts: {
21
27
  contextTokens: number;
22
28
  contextPercent: number;
@@ -26,4 +32,5 @@ export declare function showCompactUI(ctx: ExtensionCommandContext, opts: {
26
32
  model: ModelOption;
27
33
  profile: CompressionProfile;
28
34
  } | null>;
35
+ export {};
29
36
  //# sourceMappingURL=overlays.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"overlays.d.ts","sourceRoot":"","sources":["../../src/ui/overlays.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAI/E,OAAO,KAAK,EACV,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAC9C,mBAAmB,EAAE,oBAAoB,EAC1C,MAAM,aAAa,CAAC;AAIrB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAS7F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAO5G;AAED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,uBAAuB,EAC5B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GACvG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA8C7B;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,uBAAuB,EAC5B,aAAa,EAAE,WAAW,EAC1B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA4CpC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAM5F;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,uBAAuB,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA0Jf;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,uBAAuB,EAC5B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GACvG,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAAG,IAAI,CAAC,CAMrE"}
1
+ {"version":3,"file":"overlays.d.ts","sourceRoot":"","sources":["../../src/ui/overlays.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAI/E,OAAO,KAAK,EACV,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EACnE,mBAAmB,EAAE,oBAAoB,EAC1C,MAAM,aAAa,CAAC;AAcrB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAS7F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAO5G;AAED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,uBAAuB,EAC5B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GACvG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA8C7B;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,uBAAuB,EAC5B,aAAa,EAAE,WAAW,EAC1B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA4CpC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAM5F;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,uBAAuB,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA8Jf;AAGD,KAAK,eAAe,GAAG,MAAM,GAAG,IAAI,CAAC;AAErC,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,uBAAuB,EAC5B,IAAI,EAAE;IAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClF,OAAO,CAAC,eAAe,CAAC,CA+F1B;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,uBAAuB,EAC5B,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GACvG,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAAG,IAAI,CAAC,CAMrE"}
@@ -11,6 +11,7 @@ export declare function cacheOpts(opts: CacheAwareOptions, provider?: string, ph
11
11
  export declare function resetMetrics(): void;
12
12
  export declare function recordMetric(m: LLMCallMetric): void;
13
13
  export declare function getMetrics(): LLMCallMetric[];
14
+ export declare function effectivePromptInputTokens(inputTokens: number, cacheHitTokens: number): number;
14
15
  export declare function getMetricsSummary(): {
15
16
  totalCalls: number;
16
17
  totalInput: number;
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,iBAAiB,EAAuB,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGtJ,OAAO,EAAY,KAAK,KAAK,EAAE,KAAK,GAAG,EAAE,KAAK,gBAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAO5G,wBAAgB,mBAAmB,IAAI,MAAM,CAK5C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AASD,wBAAgB,SAAS,CACvB,IAAI,EAAE,iBAAiB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAY5C;AAKD,wBAAgB,YAAY,IAAI,IAAI,CAAyB;AAC7D,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAA4F;AAChJ,wBAAgB,UAAU,IAAI,aAAa,EAAE,CAA0B;AAEvE,wBAAgB,iBAAiB,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAYpK;AAGD,wBAAsB,eAAe,CACnC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,gBAAgB,CAAC,CA4B3B;AAWD,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD;AAED,wBAAgB,uBAAuB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAO3F;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAA4B;AAC5E,wBAAgB,yBAAyB,IAAI,IAAI,CAA8B;AAE/E;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,EACnB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,IAAI,CASN;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAQ/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAgCpI;AAGD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,cAAc,CAAC,CAAC,GAC7J,IAAI,CAaN;AAED,wBAAgB,cAAc,CAAC,KAAK,SAAM,GAAG,mBAAmB,EAAE,CAcjE;AA0MD,wBAAgB,kBAAkB,CAAC,OAAO,wBAAsB,GAAG,MAAM,CAwBxE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,wBAAsB,GAAG,MAAM,GAAG,IAAI,CAgClF"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,iBAAiB,EAAuB,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGtJ,OAAO,EAAY,KAAK,KAAK,EAAE,KAAK,GAAG,EAAE,KAAK,gBAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAO5G,wBAAgB,mBAAmB,IAAI,MAAM,CAK5C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AASD,wBAAgB,SAAS,CACvB,IAAI,EAAE,iBAAiB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAY5C;AAKD,wBAAgB,YAAY,IAAI,IAAI,CAAyB;AAC7D,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAA4F;AAChJ,wBAAgB,UAAU,IAAI,aAAa,EAAE,CAA0B;AAEvE,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAO9F;AAED,wBAAgB,iBAAiB,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAapK;AAGD,wBAAsB,eAAe,CACnC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,gBAAgB,CAAC,CA4B3B;AAWD,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD;AAED,wBAAgB,uBAAuB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAO3F;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAA4B;AAC5E,wBAAgB,yBAAyB,IAAI,IAAI,CAA8B;AAE/E;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,oBAAoB,EAChC,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,EACnB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,IAAI,CASN;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAQ/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAgCpI;AAGD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,cAAc,CAAC,CAAC,GAC7J,IAAI,CAaN;AAED,wBAAgB,cAAc,CAAC,KAAK,SAAM,GAAG,mBAAmB,EAAE,CAcjE;AA2MD,wBAAgB,kBAAkB,CAAC,OAAO,wBAAsB,GAAG,MAAM,CAwBxE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,wBAAsB,GAAG,MAAM,GAAG,IAAI,CAgClF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-smart-compact",
3
- "version": "7.12.5",
3
+ "version": "7.13.0",
4
4
  "description": "EESV smart compaction extension for Pi Coding Agent — deterministic extraction, exploration, synthesis, verification with redundancy pruning, project fingerprinting, and damage detection.",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",