pi-mega-compact 0.6.4 → 0.6.6
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/dist/extensions/dashboard-server.js +60 -0
- package/dist/extensions/mega-runtime.js +29 -17
- package/dist/src/compact.js +7 -1
- package/dist/src/supersede.js +3 -0
- package/extensions/dashboard-server.ts +60 -0
- package/extensions/mega-runtime.ts +27 -17
- package/package.json +1 -1
- package/src/compact.ts +7 -2
- package/src/supersede.ts +3 -0
|
@@ -436,6 +436,23 @@ function dashboardHtml(tierName) {
|
|
|
436
436
|
<div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
|
|
437
437
|
<div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
|
|
438
438
|
</div>
|
|
439
|
+
|
|
440
|
+
<h2 style="margin-top:24px;font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px">Savings by Model</h2>
|
|
441
|
+
<p class="legend-note" style="margin-bottom:10px">How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality.</p>
|
|
442
|
+
<table class="repos">
|
|
443
|
+
<thead>
|
|
444
|
+
<tr>
|
|
445
|
+
<th>Model</th><th>Provider</th>
|
|
446
|
+
<th style="text-align:right">Repos</th>
|
|
447
|
+
<th style="text-align:right">Checkpoints</th>
|
|
448
|
+
<th style="text-align:right">Tokens Saved</th>
|
|
449
|
+
<th style="text-align:right">$ Saved</th>
|
|
450
|
+
<th style="text-align:right">Last Used</th>
|
|
451
|
+
</tr>
|
|
452
|
+
</thead>
|
|
453
|
+
<tbody id="bm-rows"><tr><td colspan="7" class="repo-none">loading…</td></tr></tbody>
|
|
454
|
+
</table>
|
|
455
|
+
|
|
439
456
|
<div class="updated" id="sm-updated"></div>
|
|
440
457
|
</div>
|
|
441
458
|
|
|
@@ -644,6 +661,49 @@ function dashboardHtml(tierName) {
|
|
|
644
661
|
document.getElementById('cur-updated').textContent = stamp;
|
|
645
662
|
document.getElementById('all-updated').textContent = stamp;
|
|
646
663
|
document.getElementById('sm-updated').textContent = stamp;
|
|
664
|
+
renderByModel(repos);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Savings-by-model aggregation for the Summary tab — groups the machine-
|
|
668
|
+
// wide repo registry by (modelName || '(unknown)') so the user can see how
|
|
669
|
+
// much context + cost mega-compact has reclaimed, broken down by which model
|
|
670
|
+
// they were running. $ Saved = Σ(tokensSaved × inputRate) per model. Sorted
|
|
671
|
+
// by tokens saved descending so the biggest-reclaim model wins the top row.
|
|
672
|
+
function renderByModel(repos) {
|
|
673
|
+
var rows = document.getElementById('bm-rows');
|
|
674
|
+
if (!rows) return;
|
|
675
|
+
if (!repos || !repos.length) {
|
|
676
|
+
rows.innerHTML = '<tr><td colspan="7" class="repo-none">No repositories registered yet.</td></tr>';
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
var groups = {};
|
|
680
|
+
for (var i = 0; i < repos.length; i++) {
|
|
681
|
+
var r = repos[i];
|
|
682
|
+
var key = (r.modelName && String(r.modelName).trim()) || '(unknown)';
|
|
683
|
+
if (!groups[key]) groups[key] = { model: key, provider: r.providerName || r.provider || '—', repos: 0, checkpoints: 0, tokensSaved: 0, usd: 0, lastAt: 0, rates: [] };
|
|
684
|
+
var g = groups[key];
|
|
685
|
+
g.repos++;
|
|
686
|
+
g.checkpoints += (r.checkpointCount || 0);
|
|
687
|
+
g.tokensSaved += (r.tokensSaved || 0);
|
|
688
|
+
if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.rates.push(r.inputRate); }
|
|
689
|
+
if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt;
|
|
690
|
+
}
|
|
691
|
+
var arr = [];
|
|
692
|
+
for (var k in groups) { if (Object.prototype.hasOwnProperty.call(groups, k)) arr.push(groups[k]); }
|
|
693
|
+
arr.sort(function(a, b) { return b.tokensSaved - a.tokensSaved; });
|
|
694
|
+
rows.innerHTML = arr.map(function(g) {
|
|
695
|
+
var usd = g.usd > 0 ? '$' + g.usd.toFixed(4) : '—';
|
|
696
|
+
var when = g.lastAt ? new Date(g.lastAt).toLocaleString() : '—';
|
|
697
|
+
return '<tr>' +
|
|
698
|
+
'<td><span class="repo-model">' + sanitize(g.model) + '</span></td>' +
|
|
699
|
+
'<td>' + sanitize(g.provider) + '</td>' +
|
|
700
|
+
'<td class="num">' + g.repos.toLocaleString() + '</td>' +
|
|
701
|
+
'<td class="num">' + g.checkpoints.toLocaleString() + '</td>' +
|
|
702
|
+
'<td class="num">' + g.tokensSaved.toLocaleString() + '</td>' +
|
|
703
|
+
'<td class="num">' + sanitize(usd) + '</td>' +
|
|
704
|
+
'<td class="num">' + sanitize(when) + '</td>' +
|
|
705
|
+
'</tr>';
|
|
706
|
+
}).join('');
|
|
647
707
|
}
|
|
648
708
|
|
|
649
709
|
// Per-repo detail modal ---------------------------------------------------
|
|
@@ -53,6 +53,7 @@ export const C = {
|
|
|
53
53
|
magenta: "\x1b[38;5;201m", // dedup rate
|
|
54
54
|
blue: "\x1b[38;5;75m", // repo totals
|
|
55
55
|
gray: "\x1b[38;5;245m", // labels
|
|
56
|
+
red: "\x1b[38;5;203m", // pressure / overflow
|
|
56
57
|
};
|
|
57
58
|
const PULSE = ["◐", "◓", "◑", "◒"];
|
|
58
59
|
export class MegaRuntime {
|
|
@@ -315,7 +316,7 @@ export class MegaRuntime {
|
|
|
315
316
|
// 1e6, k at/above 1e3, raw below — so 5,472,700 → "5.5M", 24,100 → "24.1k",
|
|
316
317
|
// 142 → "142". Dropped (in) = Freed + Kept; Freed = rt.tokensSaved (session)
|
|
317
318
|
// / repo.tokensSaved meta (repo); Kept = totalTokenEstimate (stored).
|
|
318
|
-
const fmt = (x) => x >= 1_000_000 ? `${(x / 1_000_000).toFixed(1)}
|
|
319
|
+
const fmt = (x) => x >= 1_000_000 ? `${(x / 1_000_000).toFixed(1)}mil`
|
|
319
320
|
: x >= 1000 ? `${(x / 1000).toFixed(1)}k`
|
|
320
321
|
: `${Math.round(x)}`;
|
|
321
322
|
const agentStr = this.activeAgents > 0 ? ` │ 🤖 ${this.activeAgents} agent${this.activeAgents === 1 ? "" : "s"}` : "";
|
|
@@ -331,21 +332,32 @@ export class MegaRuntime {
|
|
|
331
332
|
const repoKept = repo.totalTokenEstimate;
|
|
332
333
|
const repoFreed = repo.tokensSaved;
|
|
333
334
|
const repoPct = repoIn > 0 ? repoFreed / repoIn : 0;
|
|
335
|
+
// Retro gradient bar — 12 cells, each cell shaded by fill so it reads as a
|
|
336
|
+
// smooth green→amber→red ramp instead of a flat block. Higher fill = more
|
|
337
|
+
// reclaimed, so the bar trends green at the right end.
|
|
338
|
+
const ramp = (pct, w = 12) => {
|
|
339
|
+
const cells = ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
|
|
340
|
+
const scaled = Math.max(0, Math.min(w, pct * w));
|
|
341
|
+
const full = Math.floor(scaled);
|
|
342
|
+
const frac = scaled - full;
|
|
343
|
+
const fracCell = frac > 0 ? cells[Math.round(frac * (cells.length - 1))] : "";
|
|
344
|
+
let out = "";
|
|
345
|
+
for (let i = 0; i < full; i++)
|
|
346
|
+
out += (i / w < 0.6 ? C.green : i / w < 0.85 ? C.amber : C.red) + "█";
|
|
347
|
+
if (fracCell)
|
|
348
|
+
out += (full / w < 0.6 ? C.green : full / w < 0.85 ? C.amber : C.red) + fracCell;
|
|
349
|
+
out += C.dim + "░".repeat(Math.max(0, w - full - (fracCell ? 1 : 0))) + C.reset;
|
|
350
|
+
return out;
|
|
351
|
+
};
|
|
352
|
+
const ctxPct = this.lastCtxPercent != null ? this.lastCtxPercent / 100 : 0;
|
|
353
|
+
const sTxt = (sessPct * 100).toFixed(sessPct * 100 >= 10 ? 0 : 1);
|
|
354
|
+
const rTxt = (repoPct * 100).toFixed(repoPct * 100 >= 10 ? 0 : 1);
|
|
334
355
|
const lines = [
|
|
335
|
-
|
|
336
|
-
`
|
|
337
|
-
|
|
356
|
+
// L1 — header: tier + ctx fill bar + tokens + checkpoints + agents
|
|
357
|
+
` ${C.amber}⚡ ${tierLabel}${C.reset} v${C.bold}${ownVersion()}${C.reset} ${ramp(ctxPct)} ${C.bold}${pctStr}${C.reset} ${tokStr}/${maxStr} │ ${st.checkpointCount} chk${agentStr}${turnStr}`,
|
|
358
|
+
// L2 — status + dedup + session + all-time savings bars
|
|
359
|
+
` ${triggerLabel} ${C.magenta}dup ${dedupStr}${C.reset} ${C.gray}sess${C.reset} ${ramp(sessPct)} ${C.green}${sTxt}%${C.reset} ${C.gray}all-time${C.reset} ${ramp(repoPct)} ${C.blue}${rTxt}%${C.reset}`,
|
|
338
360
|
];
|
|
339
|
-
// Compression meter — the single headline "% tokens saved" (Freed / In),
|
|
340
|
-
// same formula as the dashboard. Higher = better, so it reads green.
|
|
341
|
-
{
|
|
342
|
-
const w = 10;
|
|
343
|
-
const filled = Math.max(0, Math.min(w, Math.round(sessPct * w)));
|
|
344
|
-
const cbar = C.green + "▓".repeat(filled) + C.dim + "░".repeat(w - filled) + C.reset;
|
|
345
|
-
const sTxt = (sessPct * 100).toFixed(sessPct * 100 >= 10 ? 0 : 1);
|
|
346
|
-
const rTxt = (repoPct * 100).toFixed(repoPct * 100 >= 10 ? 0 : 1);
|
|
347
|
-
lines.push(` ${cbar} ${sTxt}% tokens saved (sess) · ${rTxt}% repo${C.reset}`);
|
|
348
|
-
}
|
|
349
361
|
// Live "now processing" line + why + recent deduped/compacted events,
|
|
350
362
|
// collapsed to ONE rotating line (fresh only). The ticker ring buffer
|
|
351
363
|
// (≤5 most-recent events) is cycled one-per-repaint so the line scrolls
|
|
@@ -367,10 +379,10 @@ export class MegaRuntime {
|
|
|
367
379
|
else if (this.pulsing) {
|
|
368
380
|
lines.push(` ${pulse}${C.teal}compacting…${C.reset}`);
|
|
369
381
|
}
|
|
370
|
-
//
|
|
371
|
-
//
|
|
382
|
+
// L4 — accounting: session + all-time in/out/freed, one compact line.
|
|
383
|
+
// in = dropped into compaction, out = kept summaries, freed = saved.
|
|
372
384
|
if (lines.length < 10) {
|
|
373
|
-
lines.push(` ${C.dim}
|
|
385
|
+
lines.push(` ${C.dim}session ↑${fmt(sessIn)} in ↓${fmt(sessKept)} out ↓${fmt(sessFreed)} freed · all-time ↑${fmt(repoIn)} in ↓${fmt(repoKept)} out ↓${fmt(repoFreed)} freed${C.reset}`);
|
|
374
386
|
}
|
|
375
387
|
ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "aboveEditor" });
|
|
376
388
|
}
|
package/dist/src/compact.js
CHANGED
|
@@ -27,8 +27,14 @@ export function isChatty(text) {
|
|
|
27
27
|
}
|
|
28
28
|
return text.length < 40 && !/(\/|\.|\{|import |def |function )/.test(text);
|
|
29
29
|
}
|
|
30
|
-
/** Extract plausible file paths (contain '/' + an interesting extension).
|
|
30
|
+
/** Extract plausible file paths (contain '/' + an interesting extension).
|
|
31
|
+
* Defensive against a missing/empty payload: pi's adapter can hand the engine
|
|
32
|
+
* a message whose `text`/`input`/`output` is undefined (e.g. a pure tool-call
|
|
33
|
+
* or tool-result message), and `.split` on undefined throws and takes down the
|
|
34
|
+
* whole compaction. Guard once at the source so every caller is safe. */
|
|
31
35
|
export function extractFileCandidates(content) {
|
|
36
|
+
if (!content)
|
|
37
|
+
return [];
|
|
32
38
|
const out = [];
|
|
33
39
|
for (const raw of content.split(/\s+/)) {
|
|
34
40
|
// Trim surrounding punctuation only — do NOT strip internal dots, or we
|
package/dist/src/supersede.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { extractFileCandidates } from "./compact.js";
|
|
10
10
|
/** Classify a message's relationship to a file path. */
|
|
11
11
|
function fileOps(msg) {
|
|
12
|
+
// msg.text may be undefined for pure tool-call/result messages; the guard
|
|
13
|
+
// lives in extractFileCandidates, but the early return short-circuits the
|
|
14
|
+
// write-detection regex too so we never classify an empty message.
|
|
12
15
|
const paths = extractFileCandidates(msg.text);
|
|
13
16
|
if (paths.length === 0)
|
|
14
17
|
return [];
|
|
@@ -534,6 +534,23 @@ function dashboardHtml(tierName: string): string {
|
|
|
534
534
|
<div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
|
|
535
535
|
<div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
|
|
536
536
|
</div>
|
|
537
|
+
|
|
538
|
+
<h2 style="margin-top:24px;font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px">Savings by Model</h2>
|
|
539
|
+
<p class="legend-note" style="margin-bottom:10px">How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality.</p>
|
|
540
|
+
<table class="repos">
|
|
541
|
+
<thead>
|
|
542
|
+
<tr>
|
|
543
|
+
<th>Model</th><th>Provider</th>
|
|
544
|
+
<th style="text-align:right">Repos</th>
|
|
545
|
+
<th style="text-align:right">Checkpoints</th>
|
|
546
|
+
<th style="text-align:right">Tokens Saved</th>
|
|
547
|
+
<th style="text-align:right">$ Saved</th>
|
|
548
|
+
<th style="text-align:right">Last Used</th>
|
|
549
|
+
</tr>
|
|
550
|
+
</thead>
|
|
551
|
+
<tbody id="bm-rows"><tr><td colspan="7" class="repo-none">loading…</td></tr></tbody>
|
|
552
|
+
</table>
|
|
553
|
+
|
|
537
554
|
<div class="updated" id="sm-updated"></div>
|
|
538
555
|
</div>
|
|
539
556
|
|
|
@@ -742,6 +759,49 @@ function dashboardHtml(tierName: string): string {
|
|
|
742
759
|
document.getElementById('cur-updated').textContent = stamp;
|
|
743
760
|
document.getElementById('all-updated').textContent = stamp;
|
|
744
761
|
document.getElementById('sm-updated').textContent = stamp;
|
|
762
|
+
renderByModel(repos);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Savings-by-model aggregation for the Summary tab — groups the machine-
|
|
766
|
+
// wide repo registry by (modelName || '(unknown)') so the user can see how
|
|
767
|
+
// much context + cost mega-compact has reclaimed, broken down by which model
|
|
768
|
+
// they were running. $ Saved = Σ(tokensSaved × inputRate) per model. Sorted
|
|
769
|
+
// by tokens saved descending so the biggest-reclaim model wins the top row.
|
|
770
|
+
function renderByModel(repos) {
|
|
771
|
+
var rows = document.getElementById('bm-rows');
|
|
772
|
+
if (!rows) return;
|
|
773
|
+
if (!repos || !repos.length) {
|
|
774
|
+
rows.innerHTML = '<tr><td colspan="7" class="repo-none">No repositories registered yet.</td></tr>';
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
var groups = {};
|
|
778
|
+
for (var i = 0; i < repos.length; i++) {
|
|
779
|
+
var r = repos[i];
|
|
780
|
+
var key = (r.modelName && String(r.modelName).trim()) || '(unknown)';
|
|
781
|
+
if (!groups[key]) groups[key] = { model: key, provider: r.providerName || r.provider || '—', repos: 0, checkpoints: 0, tokensSaved: 0, usd: 0, lastAt: 0, rates: [] };
|
|
782
|
+
var g = groups[key];
|
|
783
|
+
g.repos++;
|
|
784
|
+
g.checkpoints += (r.checkpointCount || 0);
|
|
785
|
+
g.tokensSaved += (r.tokensSaved || 0);
|
|
786
|
+
if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.rates.push(r.inputRate); }
|
|
787
|
+
if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt;
|
|
788
|
+
}
|
|
789
|
+
var arr = [];
|
|
790
|
+
for (var k in groups) { if (Object.prototype.hasOwnProperty.call(groups, k)) arr.push(groups[k]); }
|
|
791
|
+
arr.sort(function(a, b) { return b.tokensSaved - a.tokensSaved; });
|
|
792
|
+
rows.innerHTML = arr.map(function(g) {
|
|
793
|
+
var usd = g.usd > 0 ? '$' + g.usd.toFixed(4) : '—';
|
|
794
|
+
var when = g.lastAt ? new Date(g.lastAt).toLocaleString() : '—';
|
|
795
|
+
return '<tr>' +
|
|
796
|
+
'<td><span class="repo-model">' + sanitize(g.model) + '</span></td>' +
|
|
797
|
+
'<td>' + sanitize(g.provider) + '</td>' +
|
|
798
|
+
'<td class="num">' + g.repos.toLocaleString() + '</td>' +
|
|
799
|
+
'<td class="num">' + g.checkpoints.toLocaleString() + '</td>' +
|
|
800
|
+
'<td class="num">' + g.tokensSaved.toLocaleString() + '</td>' +
|
|
801
|
+
'<td class="num">' + sanitize(usd) + '</td>' +
|
|
802
|
+
'<td class="num">' + sanitize(when) + '</td>' +
|
|
803
|
+
'</tr>';
|
|
804
|
+
}).join('');
|
|
745
805
|
}
|
|
746
806
|
|
|
747
807
|
// Per-repo detail modal ---------------------------------------------------
|
|
@@ -69,6 +69,7 @@ export const C = {
|
|
|
69
69
|
magenta: "\x1b[38;5;201m", // dedup rate
|
|
70
70
|
blue: "\x1b[38;5;75m", // repo totals
|
|
71
71
|
gray: "\x1b[38;5;245m", // labels
|
|
72
|
+
red: "\x1b[38;5;203m", // pressure / overflow
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
const PULSE = ["◐", "◓", "◑", "◒"];
|
|
@@ -344,7 +345,7 @@ export class MegaRuntime {
|
|
|
344
345
|
// 142 → "142". Dropped (in) = Freed + Kept; Freed = rt.tokensSaved (session)
|
|
345
346
|
// / repo.tokensSaved meta (repo); Kept = totalTokenEstimate (stored).
|
|
346
347
|
const fmt = (x: number) =>
|
|
347
|
-
x >= 1_000_000 ? `${(x / 1_000_000).toFixed(1)}
|
|
348
|
+
x >= 1_000_000 ? `${(x / 1_000_000).toFixed(1)}mil`
|
|
348
349
|
: x >= 1000 ? `${(x / 1000).toFixed(1)}k`
|
|
349
350
|
: `${Math.round(x)}`;
|
|
350
351
|
const agentStr = this.activeAgents > 0 ? ` │ 🤖 ${this.activeAgents} agent${this.activeAgents === 1 ? "" : "s"}` : "";
|
|
@@ -360,21 +361,30 @@ export class MegaRuntime {
|
|
|
360
361
|
const repoKept = repo.totalTokenEstimate;
|
|
361
362
|
const repoFreed = repo.tokensSaved;
|
|
362
363
|
const repoPct = repoIn > 0 ? repoFreed / repoIn : 0;
|
|
364
|
+
// Retro gradient bar — 12 cells, each cell shaded by fill so it reads as a
|
|
365
|
+
// smooth green→amber→red ramp instead of a flat block. Higher fill = more
|
|
366
|
+
// reclaimed, so the bar trends green at the right end.
|
|
367
|
+
const ramp = (pct: number, w = 12): string => {
|
|
368
|
+
const cells = ["▏","▎","▍","▌","▋","▊","▉","█"];
|
|
369
|
+
const scaled = Math.max(0, Math.min(w, pct * w));
|
|
370
|
+
const full = Math.floor(scaled);
|
|
371
|
+
const frac = scaled - full;
|
|
372
|
+
const fracCell = frac > 0 ? cells[Math.round(frac * (cells.length - 1))] : "";
|
|
373
|
+
let out = "";
|
|
374
|
+
for (let i = 0; i < full; i++) out += (i / w < 0.6 ? C.green : i / w < 0.85 ? C.amber : C.red) + "█";
|
|
375
|
+
if (fracCell) out += (full / w < 0.6 ? C.green : full / w < 0.85 ? C.amber : C.red) + fracCell;
|
|
376
|
+
out += C.dim + "░".repeat(Math.max(0, w - full - (fracCell ? 1 : 0))) + C.reset;
|
|
377
|
+
return out;
|
|
378
|
+
};
|
|
379
|
+
const ctxPct = this.lastCtxPercent != null ? this.lastCtxPercent / 100 : 0;
|
|
380
|
+
const sTxt = (sessPct * 100).toFixed(sessPct * 100 >= 10 ? 0 : 1);
|
|
381
|
+
const rTxt = (repoPct * 100).toFixed(repoPct * 100 >= 10 ? 0 : 1);
|
|
363
382
|
const lines = [
|
|
364
|
-
|
|
365
|
-
`
|
|
366
|
-
|
|
383
|
+
// L1 — header: tier + ctx fill bar + tokens + checkpoints + agents
|
|
384
|
+
` ${C.amber}⚡ ${tierLabel}${C.reset} v${C.bold}${ownVersion()}${C.reset} ${ramp(ctxPct)} ${C.bold}${pctStr}${C.reset} ${tokStr}/${maxStr} │ ${st.checkpointCount} chk${agentStr}${turnStr}`,
|
|
385
|
+
// L2 — status + dedup + session + all-time savings bars
|
|
386
|
+
` ${triggerLabel} ${C.magenta}dup ${dedupStr}${C.reset} ${C.gray}sess${C.reset} ${ramp(sessPct)} ${C.green}${sTxt}%${C.reset} ${C.gray}all-time${C.reset} ${ramp(repoPct)} ${C.blue}${rTxt}%${C.reset}`,
|
|
367
387
|
];
|
|
368
|
-
// Compression meter — the single headline "% tokens saved" (Freed / In),
|
|
369
|
-
// same formula as the dashboard. Higher = better, so it reads green.
|
|
370
|
-
{
|
|
371
|
-
const w = 10;
|
|
372
|
-
const filled = Math.max(0, Math.min(w, Math.round(sessPct * w)));
|
|
373
|
-
const cbar = C.green + "▓".repeat(filled) + C.dim + "░".repeat(w - filled) + C.reset;
|
|
374
|
-
const sTxt = (sessPct * 100).toFixed(sessPct * 100 >= 10 ? 0 : 1);
|
|
375
|
-
const rTxt = (repoPct * 100).toFixed(repoPct * 100 >= 10 ? 0 : 1);
|
|
376
|
-
lines.push(` ${cbar} ${sTxt}% tokens saved (sess) · ${rTxt}% repo${C.reset}`);
|
|
377
|
-
}
|
|
378
388
|
// Live "now processing" line + why + recent deduped/compacted events,
|
|
379
389
|
// collapsed to ONE rotating line (fresh only). The ticker ring buffer
|
|
380
390
|
// (≤5 most-recent events) is cycled one-per-repaint so the line scrolls
|
|
@@ -394,10 +404,10 @@ export class MegaRuntime {
|
|
|
394
404
|
} else if (this.pulsing) {
|
|
395
405
|
lines.push(` ${pulse}${C.teal}compacting…${C.reset}`);
|
|
396
406
|
}
|
|
397
|
-
//
|
|
398
|
-
//
|
|
407
|
+
// L4 — accounting: session + all-time in/out/freed, one compact line.
|
|
408
|
+
// in = dropped into compaction, out = kept summaries, freed = saved.
|
|
399
409
|
if (lines.length < 10) {
|
|
400
|
-
lines.push(` ${C.dim}
|
|
410
|
+
lines.push(` ${C.dim}session ↑${fmt(sessIn)} in ↓${fmt(sessKept)} out ↓${fmt(sessFreed)} freed · all-time ↑${fmt(repoIn)} in ↓${fmt(repoKept)} out ↓${fmt(repoFreed)} freed${C.reset}`);
|
|
401
411
|
}
|
|
402
412
|
ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "aboveEditor" });
|
|
403
413
|
}
|
package/package.json
CHANGED
package/src/compact.ts
CHANGED
|
@@ -36,8 +36,13 @@ export function isChatty(text: string): boolean {
|
|
|
36
36
|
return text.length < 40 && !/(\/|\.|\{|import |def |function )/.test(text);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/** Extract plausible file paths (contain '/' + an interesting extension).
|
|
40
|
-
|
|
39
|
+
/** Extract plausible file paths (contain '/' + an interesting extension).
|
|
40
|
+
* Defensive against a missing/empty payload: pi's adapter can hand the engine
|
|
41
|
+
* a message whose `text`/`input`/`output` is undefined (e.g. a pure tool-call
|
|
42
|
+
* or tool-result message), and `.split` on undefined throws and takes down the
|
|
43
|
+
* whole compaction. Guard once at the source so every caller is safe. */
|
|
44
|
+
export function extractFileCandidates(content: string | undefined | null): string[] {
|
|
45
|
+
if (!content) return [];
|
|
41
46
|
const out: string[] = [];
|
|
42
47
|
for (const raw of content.split(/\s+/)) {
|
|
43
48
|
// Trim surrounding punctuation only — do NOT strip internal dots, or we
|
package/src/supersede.ts
CHANGED
|
@@ -12,6 +12,9 @@ import { extractFileCandidates } from "./compact.js";
|
|
|
12
12
|
|
|
13
13
|
/** Classify a message's relationship to a file path. */
|
|
14
14
|
function fileOps(msg: EngineMessage): { path: string; op: "read" | "write" }[] {
|
|
15
|
+
// msg.text may be undefined for pure tool-call/result messages; the guard
|
|
16
|
+
// lives in extractFileCandidates, but the early return short-circuits the
|
|
17
|
+
// write-detection regex too so we never classify an empty message.
|
|
15
18
|
const paths = extractFileCandidates(msg.text);
|
|
16
19
|
if (paths.length === 0) return [];
|
|
17
20
|
const low = msg.text.toLowerCase();
|