pi-mega-compact 0.4.12 β†’ 0.4.14

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.
@@ -34,7 +34,9 @@ import { recallAndInline } from "../src/recall.js";
34
34
  import { autoCompactCheck } from "../src/compact.js";
35
35
  import { estimateSessionTokens } from "../src/tokens.js";
36
36
  import { normalizeSessionId } from "../src/store.js";
37
- import { touchSession, logDaily } from "../src/store/sqlite.js";
37
+ import { touchSession, logDaily, listCheckpoints } from "../src/store/sqlite.js";
38
+ import { decompressSmart } from "../src/store/compression.js";
39
+ import { loadMetrics, fpRate, p95 } from "../src/monitoring.js";
38
40
  import { Logger } from "../src/log.js";
39
41
  import { writeFileSync, appendFileSync, readFileSync } from "node:fs";
40
42
  import { existsSync, mkdirSync, unlinkSync } from "node:fs";
@@ -255,25 +257,43 @@ export default function (pi) {
255
257
  const usedStr = `${C.cyan}${fmt(st.totalTokenEstimate)} sess${C.reset} / ${C.blue}${fmt(repo.totalTokenEstimate)} repo${C.reset}`;
256
258
  const agentStr = activeAgents > 0 ? ` β”‚ πŸ€– ${activeAgents} agent${activeAgents === 1 ? "" : "s"}` : "";
257
259
  const turnStr = currentTurn > 0 ? ` β”‚ turn ${currentTurn}` : "";
260
+ // Phase 3 β€” pulsing status glyph while a compaction is in flight.
261
+ const pulse = pulsing ? `${C.cyan}${PULSE[Math.floor(Date.now() / 250) % PULSE.length]}${C.reset} ` : "";
258
262
  const lines = [
259
- ` ${C.amber}⚑ ${config.tier}${C.reset} β”‚ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) β”‚ ${st.checkpointCount} chkpt${st.checkpointCount === 1 ? "" : "s"}${agentStr}${turnStr}`,
263
+ ` ${C.amber}⚑ ${config.tier}${C.reset} β”‚ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) β”‚ ${st.checkpointCount} chkpt${agentStr}${turnStr}`,
260
264
  ` ${triggerLabel} β”‚ ${C.magenta}dedup: ${dedupStr}${C.reset} β”‚ ${C.gray}used:${C.reset} ${usedStr} β”‚ ${C.gray}saved:${C.reset} ${savedStr}`,
261
265
  ];
266
+ // Phase 3 β€” compact progress bar: session tokens saved toward the rolling goal.
267
+ if (rt.tokensSaved > 0) {
268
+ const goal = Math.max(savedGoal, 1);
269
+ const pct = Math.min(100, Math.round((rt.tokensSaved / goal) * 100));
270
+ const filled = Math.round((pct / 100) * 10);
271
+ const bar = "β–“".repeat(filled) + "β–‘".repeat(10 - filled);
272
+ lines.push(` ${C.green}saved ${fmt(rt.tokensSaved)} ${bar}${C.reset} ${pct}% of ${fmt(goal)}`);
273
+ }
262
274
  // Live "now processing" line β€” teal while fresh (≀4s), then the last-seen
263
275
  // action keeps the widget lively. Cleared on session reset.
264
- if (tierTrace) {
265
- // Per-tier dedup progress is more relevant than the last action while a
266
- // compaction is mid-flight, so prefer it when fresh.
267
- const fresh = Date.now() - lastActivityAt < 4000;
268
- if (fresh)
269
- lines.push(` ${tierTrace}`);
270
- else if (currentActivity)
271
- lines.push(` ${C.dim}${currentActivity}${C.reset}`);
276
+ const fresh = Date.now() - lastActivityAt < 4000;
277
+ if (tierTrace && fresh) {
278
+ lines.push(` ${pulse}${tierTrace}`);
272
279
  }
273
280
  else if (currentActivity) {
274
- const fresh = Date.now() - lastActivityAt < 4000;
275
281
  lines.push(` ${fresh ? C.teal : C.dim}${currentActivity}${C.reset}`);
276
282
  }
283
+ else if (pulsing) {
284
+ lines.push(` ${pulse}${C.teal}compacting…${C.reset}`);
285
+ }
286
+ // Phase 3 β€” explain-why line (fresh only).
287
+ if (lastWhy && fresh)
288
+ lines.push(` ${C.gray}${lastWhy}${C.reset}`);
289
+ // Phase 3 β€” recall/activity ticker (most-recent first), fresh only.
290
+ if (fresh) {
291
+ for (let i = ticker.length - 1; i >= 0; i--) {
292
+ if (lines.length >= 10)
293
+ break; // MAX_WIDGET_LINES guard
294
+ lines.push(` ${i === ticker.length - 1 ? "" : C.dim}${ticker[i].text}${C.reset}`);
295
+ }
296
+ }
277
297
  ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "aboveEditor" });
278
298
  }
279
299
  }
@@ -305,6 +325,24 @@ export default function (pi) {
305
325
  // Built from the store's sync onTier callback during a compaction so the user
306
326
  // watches each tier evaluate in real time. Cleared once the outcome settles.
307
327
  let tierTrace;
328
+ const ticker = [];
329
+ const TICKER_MAX = 5;
330
+ function pushTicker(text) {
331
+ ticker.push({ text, at: Date.now() });
332
+ while (ticker.length > TICKER_MAX)
333
+ ticker.shift();
334
+ lastActivityAt = Date.now();
335
+ }
336
+ // Pulsing status: set true while a compaction is in flight, cleared on result.
337
+ let pulsing = false;
338
+ // Rolling "saved" goal for the progress bar β€” grows as we save more, so the
339
+ // bar always has a meaningful denominator (never sits at 100% forever).
340
+ let savedGoal = 50_000;
341
+ // Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
342
+ // while fresh.
343
+ let lastWhy = undefined;
344
+ // Cycling glyph phases for the pulsing status.
345
+ const PULSE = ["◐", "β—“", "β—‘", "β—’"];
308
346
  // ANSI palette for the toolbar. The pi TUI's Text component preserves ANSI
309
347
  // escape codes (see wrapTextWithAnsi), so raw escapes render as colors. No
310
348
  // chalk dependency needed β€” these are just strings.
@@ -344,6 +382,10 @@ export default function (pi) {
344
382
  currentActivity = undefined;
345
383
  lastActivityAt = 0;
346
384
  tierTrace = undefined;
385
+ ticker.length = 0;
386
+ pulsing = false;
387
+ savedGoal = 50_000;
388
+ lastWhy = undefined;
347
389
  }
348
390
  /** Build the sync onTier callback that paints the live per-tier trace. */
349
391
  function makeTierCallback(ctx) {
@@ -382,6 +424,7 @@ export default function (pi) {
382
424
  const keepFrom = opts.keepFrom ?? Math.max(0, view.length - config.preserveRecent);
383
425
  if (keepFrom <= 0)
384
426
  return { skipped: true };
427
+ pulsing = true; // animate the status line while the (sync) pipeline runs
385
428
  const result = compactSession({
386
429
  sessionId: sid,
387
430
  messages: view,
@@ -390,6 +433,7 @@ export default function (pi) {
390
433
  timestamp: Date.now(),
391
434
  onTier: makeTierCallback(ctx),
392
435
  }, store);
436
+ pulsing = false;
393
437
  if (result.skipped)
394
438
  return { skipped: true };
395
439
  if (!result.deduped) {
@@ -410,6 +454,10 @@ export default function (pi) {
410
454
  rt.tokensSaved += saved;
411
455
  if (result.deduped)
412
456
  rt.dedupSkips++;
457
+ // Grow the rolling "saved" goal so the progress bar always has a fresh
458
+ // denominator (we don't want it pinned at 100% once we pass an old target).
459
+ if (rt.tokensSaved > savedGoal)
460
+ savedGoal = Math.ceil((rt.tokensSaved * 1.25) / 10_000) * 10_000;
413
461
  // Live toolbar "now processing" line: what file/region just got compacted or
414
462
  // deduped. Reset to the last-seen action after a few seconds (see snapshot).
415
463
  const files = result.filesModified ?? [];
@@ -420,6 +468,16 @@ export default function (pi) {
420
468
  ? `β™» deduped ${fileLabel}`
421
469
  : `πŸ—œ compacted ${result.checkpointId} Β· ${fileLabel}`;
422
470
  lastActivityAt = Date.now();
471
+ // Explain-why line: surfaced while fresh. Pulls the dedup reason (which for
472
+ // L2 includes the cosine sim) so the user sees WHY a region was kept/dropped.
473
+ lastWhy = result.deduped
474
+ ? `why: deduped@${result.dedupReason ?? "tier"}`
475
+ : `why: compacted β†’ ${result.checkpointId}`;
476
+ // Recall/activity ticker: record this event in the ring buffer.
477
+ const savedK = (saved / 1000).toFixed(1);
478
+ pushTicker(result.deduped
479
+ ? `${C.green}β™»${C.reset} deduped ${fileLabel} Β· ${savedK}k saved`
480
+ : `${C.cyan}πŸ—œ${C.reset} ${result.checkpointId} Β· +${savedK}k Β· ${fileLabel}`);
423
481
  // The per-tier trace has settled into the final outcome β€” fold it back into
424
482
  // the activity line and stop showing the live trace.
425
483
  tierTrace = undefined;
@@ -472,6 +530,14 @@ export default function (pi) {
472
530
  const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
473
531
  const result = recallAndInline({ sessionId: sid, query, limit: config.autoInlineK, source, skipInjected: true }, store);
474
532
  dashboard.event("recall", { source, query: query.slice(0, 120), injected: result.toInject.length, empty: result.empty });
533
+ if (!result.empty && result.toInject.length > 0) {
534
+ const top = result.toInject[0];
535
+ const scorePct = Math.round((top.score ?? 0) * 100);
536
+ const files = top.checkpoint.filesModified ?? [];
537
+ const label = files.length ? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ") : top.checkpoint.checkpointId;
538
+ pushTicker(`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} Β· ${scorePct}% Β· ${label}`);
539
+ lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
540
+ }
475
541
  return result;
476
542
  }
477
543
  // ---- Session lifecycle (state reset points) -------------------------------
@@ -668,9 +734,25 @@ export default function (pi) {
668
734
  const tokens = usage?.tokens != null ? `${usage.tokens} tok` : "n/a";
669
735
  const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
670
736
  const st = store.stats(sid);
737
+ const repo = store.repoStats();
671
738
  const di = store.dataInvariant();
672
739
  const fmtB = (b) => b >= 1_048_576 ? `${(b / 1_048_576).toFixed(1)} MiB` :
673
740
  b >= 1024 ? `${(b / 1024).toFixed(1)} KiB` : `${b} B`;
741
+ // Tangible cost: turn "tokens saved" into a dollar figure + context-days
742
+ // extended, so the counter is concrete rather than opaque. ~$3 / 1M tok
743
+ // (rough blended rate); contextWindow Γ· savedRate = days of context bought.
744
+ const usd = (repo.tokensSaved / 1_000_000 * 3).toFixed(2);
745
+ const ctxWindow = usage?.contextWindow ?? 0;
746
+ const daysExtended = ctxWindow > 0 && repo.tokensSaved > 0
747
+ ? (repo.tokensSaved / ctxWindow).toFixed(1)
748
+ : "0";
749
+ const costStr = `β‰ˆ $${usd} saved Β· ${daysExtended} context-windows extended`;
750
+ // Recall-quality badge (Phase 4): trust score from monitoring metrics.
751
+ const m = loadMetrics(currentStateDir);
752
+ const fp = fpRate(m, "L2");
753
+ const p95L2 = p95(m.latency.L2 ?? []);
754
+ const relPct = (st.dedupHitRate * 100).toFixed(0);
755
+ const qualityStr = `recall ${relPct}% relevant Β· FP ${(fp * 100).toFixed(1)}% Β· L2 p95 ${p95L2.toFixed(0)}ms`;
674
756
  ctx.ui.notify(`[mega-compact] pct=${pct} tokens=${tokens} tier=${config.tier} fastGate=${config.fastGatePct}% ` +
675
757
  `threshold=${config.thresholdTokens} auto=${config.auto} autoInline=${config.autoInline}\n` +
676
758
  `[mega-compact] store: ${st.checkpointCount} chkpt Β· ` +
@@ -682,9 +764,86 @@ export default function (pi) {
682
764
  `(${fmtB(di.compressedOriginalBytes)} compressed-original) Β· ` +
683
765
  `${di.duplicatesCollapsed} dedup-duplicates collapsed Β· ` +
684
766
  `${C.green}0 bytes permanently deleted${C.reset}\n` +
767
+ `[mega-compact] πŸ’° ${costStr}\n` +
768
+ `[mega-compact] 🎯 ${qualityStr}\n` +
685
769
  `[mega-compact] stateDir=${currentStateDir}`);
686
770
  },
687
771
  });
772
+ // ---- Phase 4: cheap standout commands (data is already persisted) -------
773
+ /** Resolve a checkpoint by id (or "recent"/"last") from this session's store. */
774
+ function findCheckpoint(sid, ref) {
775
+ const all = listCheckpoints(sid, currentStateDir);
776
+ if (all.length === 0)
777
+ return undefined;
778
+ if (!ref || ref === "recent" || ref === "last")
779
+ return all[all.length - 1];
780
+ return all.find((c) => c.checkpointId === ref) ?? all.find((c) => c.checkpointId.endsWith(ref));
781
+ }
782
+ pi.registerCommand("mega-restore", {
783
+ description: "Re-inject a checkpoint's verbatim original region into context. Usage: /mega-restore <chkpt|recent>",
784
+ handler: async (args, ctx) => {
785
+ bindRepo(ctx.cwd);
786
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
787
+ const cp = findCheckpoint(sid, args.trim());
788
+ if (!cp) {
789
+ ctx.ui.notify(`[mega-compact] no checkpoint found${args.trim() ? ` for "${args.trim()}"` : ""} in this session. Try /mega-history.`);
790
+ return;
791
+ }
792
+ if (!cp.compressedOriginal) {
793
+ ctx.ui.notify(`[mega-compact] ${cp.checkpointId} has no recoverable original (pre-blob or direct add). Cannot restore verbatim.`);
794
+ return;
795
+ }
796
+ const original = decompressSmart(cp.compressedOriginal).toString("utf-8");
797
+ // Re-inject verbatim via before_agent_start (PREVENT-PI-003) β€” never
798
+ // touches live messages, only prepends the restored region to systemPrompt.
799
+ pendingRecallBlock = `The following compacted context was RESTORED from checkpoint ${cp.checkpointId} (verbatim original region):\n\n${original}`;
800
+ const files = cp.filesModified?.length ? cp.filesModified.join(", ") : "(no files captured)";
801
+ ctx.ui.notify(`[mega-compact] β™» restored ${cp.checkpointId} β€” ${original.length} chars re-injected on next turn.\n` +
802
+ `[mega-compact] files: ${files}`);
803
+ dashboard.event("restore", { checkpointId: cp.checkpointId, chars: original.length });
804
+ },
805
+ });
806
+ pi.registerCommand("mega-history", {
807
+ description: "List this session's checkpoints (id, date, files, tokens). Usage: /mega-history",
808
+ handler: async (_args, ctx) => {
809
+ bindRepo(ctx.cwd);
810
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
811
+ const all = listCheckpoints(sid, currentStateDir);
812
+ if (all.length === 0) {
813
+ ctx.ui.notify("[mega-compact] no checkpoints in this session yet.");
814
+ return;
815
+ }
816
+ const rows = all.map((c) => {
817
+ const when = c.timestamp ? new Date(c.timestamp).toISOString().slice(0, 16).replace("T", " ") : "β€”";
818
+ const files = c.filesModified?.length ? c.filesModified.map((f) => f.split("/").pop()).join(", ") : "β€”";
819
+ const orig = c.originalTokenEstimate ?? 0;
820
+ const stored = c.tokenEstimate ?? 0;
821
+ const saved = Math.max(0, orig - stored);
822
+ return ` ${c.checkpointId} ${when} ${C.cyan}${saved}t saved${C.reset} ${files}`;
823
+ });
824
+ ctx.ui.notify(`[mega-compact] ${all.length} checkpoint(s) in this session:\n` + rows.join("\n") +
825
+ `\n[mega-compact] /mega-view <chkpt> to see the original region Β· /mega-restore <chkpt> to re-inject it`);
826
+ },
827
+ });
828
+ pi.registerCommand("mega-view", {
829
+ description: "Show a checkpoint's verbatim original region. Usage: /mega-view <chkpt|recent>",
830
+ handler: async (args, ctx) => {
831
+ bindRepo(ctx.cwd);
832
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
833
+ const cp = findCheckpoint(sid, args.trim());
834
+ if (!cp) {
835
+ ctx.ui.notify(`[mega-compact] no checkpoint found${args.trim() ? ` for "${args.trim()}"` : ""}. Try /mega-history.`);
836
+ return;
837
+ }
838
+ if (!cp.compressedOriginal) {
839
+ ctx.ui.notify(`[mega-compact] ${cp.checkpointId} summary:\n${cp.summary.slice(0, 500)}${cp.summary.length > 500 ? "…" : ""}\n(no verbatim original stored)`);
840
+ return;
841
+ }
842
+ const original = decompressSmart(cp.compressedOriginal).toString("utf-8");
843
+ ctx.ui.notify(`[mega-compact] ${cp.checkpointId} β€” original region (${original.length} chars):\n` +
844
+ `${original.slice(0, 1500)}${original.length > 1500 ? "\n…(truncated)" : ""}`);
845
+ },
846
+ });
688
847
  pi.registerCommand("mega-tier", {
689
848
  description: "Show or change the compaction tier at runtime. Usage: /mega-tier [low|medium|high|ultra|mega]",
690
849
  handler: async (args, ctx) => {
@@ -0,0 +1,54 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { VectorStore } from "../vectorStore.js";
7
+ import { listCheckpoints, dataInvariantStats } from "./sqlite.js";
8
+ import { decompressSmart, compressSmart } from "./compression.js";
9
+ const baseTmp = mkdtempSync(join(tmpdir(), "mc-p04-"));
10
+ let counter = 0;
11
+ function store(opts = {}) {
12
+ const dir = join(baseTmp, `run-${counter++}`);
13
+ return { s: new VectorStore({ dedupSim: opts.dedupSim ?? 0.9, stateDir: dir }), dir };
14
+ }
15
+ test("Phase 4: added checkpoint is listed and its compressed-original round-trips", () => {
16
+ const { s, dir } = store();
17
+ const original = "the original region text that gets compacted and must be restorable verbatim";
18
+ const r = s.add({ sessionId: "sess_a", summary: "s", regionText: original, tokenEstimate: 5, originalTokenEstimate: 60, timestamp: 1 });
19
+ const all = listCheckpoints("sess_a", dir);
20
+ assert.equal(all.length, 1, "one checkpoint listed");
21
+ const cp = all[0];
22
+ assert.ok(cp.compressedOriginal, "compressed-original blob present");
23
+ // The DR/restore path: decompressSmart must return the exact original.
24
+ const restored = decompressSmart(cp.compressedOriginal).toString("utf-8");
25
+ assert.equal(restored, original, "restored verbatim === original");
26
+ assert.equal(cp.checkpointId, r.checkpoint.checkpointId);
27
+ });
28
+ test("Phase 4: findCheckpoint-by-id resolves a listed checkpoint", () => {
29
+ const { s, dir } = store();
30
+ s.add({ sessionId: "sess_b", summary: "s1", regionText: "region one text content here", tokenEstimate: 4, originalTokenEstimate: 40, timestamp: 1 });
31
+ const r2 = s.add({ sessionId: "sess_b", summary: "s2", regionText: "region two text content here different", tokenEstimate: 4, originalTokenEstimate: 45, timestamp: 2 });
32
+ const all = listCheckpoints("sess_b", dir);
33
+ assert.equal(all.length, 2);
34
+ const wanted = all.find((c) => c.checkpointId === r2.checkpoint.checkpointId);
35
+ assert.ok(wanted, "checkpoint resolved by id");
36
+ assert.ok(wanted.compressedOriginal, "has restorable original");
37
+ });
38
+ test("Phase 4: dataInvariantStats sanity for restore trust (0 deleted)", () => {
39
+ const { s, dir } = store();
40
+ s.add({ sessionId: "sess_c", summary: "s", regionText: "retained region body text", tokenEstimate: 5, originalTokenEstimate: 50, timestamp: 1 });
41
+ const di = dataInvariantStats(dir);
42
+ assert.equal(di.regionsRetained, 1);
43
+ assert.ok(di.compressedOriginalBytes > 0);
44
+ assert.equal(di.bytesPermanentlyDeleted, 0);
45
+ });
46
+ test("Phase 4: compressSmart/decompressSmart round-trips arbitrary text", () => {
47
+ const text = "x".repeat(2000);
48
+ const back = decompressSmart(compressSmart(Buffer.from(text, "utf-8"))).toString("utf-8");
49
+ assert.equal(back, text);
50
+ });
51
+ process.on("exit", () => { try {
52
+ rmSync(baseTmp, { recursive: true, force: true });
53
+ }
54
+ catch { /* ignore */ } });
@@ -37,7 +37,9 @@ import { recallAndInline } from "../src/recall.js";
37
37
  import { autoCompactCheck } from "../src/compact.js";
38
38
  import { estimateSessionTokens } from "../src/tokens.js";
39
39
  import { normalizeSessionId } from "../src/store.js";
40
- import { touchSession, logDaily } from "../src/store/sqlite.js";
40
+ import { touchSession, logDaily, listCheckpoints } from "../src/store/sqlite.js";
41
+ import { decompressSmart } from "../src/store/compression.js";
42
+ import { loadMetrics, fpRate, p95 } from "../src/monitoring.js";
41
43
  import { Logger } from "../src/log.js";
42
44
  import type { EngineMessage } from "../src/types.js";
43
45
  import { writeFileSync, appendFileSync, readFileSync } from "node:fs";
@@ -358,21 +360,38 @@ export default function (pi: ExtensionAPI) {
358
360
  const usedStr = `${C.cyan}${fmt(st.totalTokenEstimate)} sess${C.reset} / ${C.blue}${fmt(repo.totalTokenEstimate)} repo${C.reset}`;
359
361
  const agentStr = activeAgents > 0 ? ` β”‚ πŸ€– ${activeAgents} agent${activeAgents === 1 ? "" : "s"}` : "";
360
362
  const turnStr = currentTurn > 0 ? ` β”‚ turn ${currentTurn}` : "";
363
+ // Phase 3 β€” pulsing status glyph while a compaction is in flight.
364
+ const pulse = pulsing ? `${C.cyan}${PULSE[Math.floor(Date.now() / 250) % PULSE.length]}${C.reset} ` : "";
361
365
  const lines = [
362
- ` ${C.amber}⚑ ${config.tier}${C.reset} β”‚ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) β”‚ ${st.checkpointCount} chkpt${st.checkpointCount === 1 ? "" : "s"}${agentStr}${turnStr}`,
366
+ ` ${C.amber}⚑ ${config.tier}${C.reset} β”‚ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) β”‚ ${st.checkpointCount} chkpt${agentStr}${turnStr}`,
363
367
  ` ${triggerLabel} β”‚ ${C.magenta}dedup: ${dedupStr}${C.reset} β”‚ ${C.gray}used:${C.reset} ${usedStr} β”‚ ${C.gray}saved:${C.reset} ${savedStr}`,
364
368
  ];
369
+ // Phase 3 β€” compact progress bar: session tokens saved toward the rolling goal.
370
+ if (rt.tokensSaved > 0) {
371
+ const goal = Math.max(savedGoal, 1);
372
+ const pct = Math.min(100, Math.round((rt.tokensSaved / goal) * 100));
373
+ const filled = Math.round((pct / 100) * 10);
374
+ const bar = "β–“".repeat(filled) + "β–‘".repeat(10 - filled);
375
+ lines.push(` ${C.green}saved ${fmt(rt.tokensSaved)} ${bar}${C.reset} ${pct}% of ${fmt(goal)}`);
376
+ }
365
377
  // Live "now processing" line β€” teal while fresh (≀4s), then the last-seen
366
378
  // action keeps the widget lively. Cleared on session reset.
367
- if (tierTrace) {
368
- // Per-tier dedup progress is more relevant than the last action while a
369
- // compaction is mid-flight, so prefer it when fresh.
370
- const fresh = Date.now() - lastActivityAt < 4000;
371
- if (fresh) lines.push(` ${tierTrace}`);
372
- else if (currentActivity) lines.push(` ${C.dim}${currentActivity}${C.reset}`);
379
+ const fresh = Date.now() - lastActivityAt < 4000;
380
+ if (tierTrace && fresh) {
381
+ lines.push(` ${pulse}${tierTrace}`);
373
382
  } else if (currentActivity) {
374
- const fresh = Date.now() - lastActivityAt < 4000;
375
383
  lines.push(` ${fresh ? C.teal : C.dim}${currentActivity}${C.reset}`);
384
+ } else if (pulsing) {
385
+ lines.push(` ${pulse}${C.teal}compacting…${C.reset}`);
386
+ }
387
+ // Phase 3 β€” explain-why line (fresh only).
388
+ if (lastWhy && fresh) lines.push(` ${C.gray}${lastWhy}${C.reset}`);
389
+ // Phase 3 β€” recall/activity ticker (most-recent first), fresh only.
390
+ if (fresh) {
391
+ for (let i = ticker.length - 1; i >= 0; i--) {
392
+ if (lines.length >= 10) break; // MAX_WIDGET_LINES guard
393
+ lines.push(` ${i === ticker.length - 1 ? "" : C.dim}${ticker[i].text}${C.reset}`);
394
+ }
376
395
  }
377
396
  ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "aboveEditor" });
378
397
  }
@@ -406,6 +425,27 @@ export default function (pi: ExtensionAPI) {
406
425
  // Built from the store's sync onTier callback during a compaction so the user
407
426
  // watches each tier evaluate in real time. Cleared once the outcome settles.
408
427
  let tierTrace: string | undefined;
428
+ // Phase 3 β€” standout toolbar state.
429
+ // Recall/activity ticker: a small ring buffer (≀5) of recent compact/recall
430
+ // events so the widget shows a live history instead of a single last action.
431
+ interface TickerEntry { text: string; at: number; }
432
+ const ticker: TickerEntry[] = [];
433
+ const TICKER_MAX = 5;
434
+ function pushTicker(text: string): void {
435
+ ticker.push({ text, at: Date.now() });
436
+ while (ticker.length > TICKER_MAX) ticker.shift();
437
+ lastActivityAt = Date.now();
438
+ }
439
+ // Pulsing status: set true while a compaction is in flight, cleared on result.
440
+ let pulsing = false;
441
+ // Rolling "saved" goal for the progress bar β€” grows as we save more, so the
442
+ // bar always has a meaningful denominator (never sits at 100% forever).
443
+ let savedGoal = 50_000;
444
+ // Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
445
+ // while fresh.
446
+ let lastWhy: string | undefined = undefined;
447
+ // Cycling glyph phases for the pulsing status.
448
+ const PULSE = ["◐", "β—“", "β—‘", "β—’"];
409
449
  // ANSI palette for the toolbar. The pi TUI's Text component preserves ANSI
410
450
  // escape codes (see wrapTextWithAnsi), so raw escapes render as colors. No
411
451
  // chalk dependency needed β€” these are just strings.
@@ -446,6 +486,10 @@ export default function (pi: ExtensionAPI) {
446
486
  currentActivity = undefined;
447
487
  lastActivityAt = 0;
448
488
  tierTrace = undefined;
489
+ ticker.length = 0;
490
+ pulsing = false;
491
+ savedGoal = 50_000;
492
+ lastWhy = undefined;
449
493
  }
450
494
 
451
495
  /** Build the sync onTier callback that paints the live per-tier trace. */
@@ -488,6 +532,7 @@ export default function (pi: ExtensionAPI) {
488
532
  const keepFrom = opts.keepFrom ?? Math.max(0, view.length - config.preserveRecent);
489
533
  if (keepFrom <= 0) return { skipped: true as const };
490
534
 
535
+ pulsing = true; // animate the status line while the (sync) pipeline runs
491
536
  const result = compactSession(
492
537
  {
493
538
  sessionId: sid,
@@ -499,6 +544,7 @@ export default function (pi: ExtensionAPI) {
499
544
  },
500
545
  store,
501
546
  );
547
+ pulsing = false;
502
548
 
503
549
  if (result.skipped) return { skipped: true as const };
504
550
  if (!result.deduped) {
@@ -518,6 +564,9 @@ export default function (pi: ExtensionAPI) {
518
564
  : Math.max(0, result.originalTokenEstimate - result.tokenEstimate);
519
565
  rt.tokensSaved += saved;
520
566
  if (result.deduped) rt.dedupSkips++;
567
+ // Grow the rolling "saved" goal so the progress bar always has a fresh
568
+ // denominator (we don't want it pinned at 100% once we pass an old target).
569
+ if (rt.tokensSaved > savedGoal) savedGoal = Math.ceil((rt.tokensSaved * 1.25) / 10_000) * 10_000;
521
570
 
522
571
  // Live toolbar "now processing" line: what file/region just got compacted or
523
572
  // deduped. Reset to the last-seen action after a few seconds (see snapshot).
@@ -529,6 +578,18 @@ export default function (pi: ExtensionAPI) {
529
578
  ? `β™» deduped ${fileLabel}`
530
579
  : `πŸ—œ compacted ${result.checkpointId} Β· ${fileLabel}`;
531
580
  lastActivityAt = Date.now();
581
+ // Explain-why line: surfaced while fresh. Pulls the dedup reason (which for
582
+ // L2 includes the cosine sim) so the user sees WHY a region was kept/dropped.
583
+ lastWhy = result.deduped
584
+ ? `why: deduped@${result.dedupReason ?? "tier"}`
585
+ : `why: compacted β†’ ${result.checkpointId}`;
586
+ // Recall/activity ticker: record this event in the ring buffer.
587
+ const savedK = (saved / 1000).toFixed(1);
588
+ pushTicker(
589
+ result.deduped
590
+ ? `${C.green}β™»${C.reset} deduped ${fileLabel} Β· ${savedK}k saved`
591
+ : `${C.cyan}πŸ—œ${C.reset} ${result.checkpointId} Β· +${savedK}k Β· ${fileLabel}`,
592
+ );
532
593
  // The per-tier trace has settled into the final outcome β€” fold it back into
533
594
  // the activity line and stop showing the live trace.
534
595
  tierTrace = undefined;
@@ -590,6 +651,14 @@ export default function (pi: ExtensionAPI) {
590
651
  store,
591
652
  );
592
653
  dashboard.event("recall", { source, query: query.slice(0, 120), injected: result.toInject.length, empty: result.empty });
654
+ if (!result.empty && result.toInject.length > 0) {
655
+ const top = result.toInject[0];
656
+ const scorePct = Math.round((top.score ?? 0) * 100);
657
+ const files = top.checkpoint.filesModified ?? [];
658
+ const label = files.length ? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ") : top.checkpoint.checkpointId;
659
+ pushTicker(`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} Β· ${scorePct}% Β· ${label}`);
660
+ lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
661
+ }
593
662
  return result;
594
663
  }
595
664
 
@@ -802,10 +871,26 @@ export default function (pi: ExtensionAPI) {
802
871
  const tokens = usage?.tokens != null ? `${usage.tokens} tok` : "n/a";
803
872
  const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
804
873
  const st = store.stats(sid);
874
+ const repo = store.repoStats();
805
875
  const di = store.dataInvariant();
806
876
  const fmtB = (b: number) =>
807
877
  b >= 1_048_576 ? `${(b / 1_048_576).toFixed(1)} MiB` :
808
878
  b >= 1024 ? `${(b / 1024).toFixed(1)} KiB` : `${b} B`;
879
+ // Tangible cost: turn "tokens saved" into a dollar figure + context-days
880
+ // extended, so the counter is concrete rather than opaque. ~$3 / 1M tok
881
+ // (rough blended rate); contextWindow Γ· savedRate = days of context bought.
882
+ const usd = (repo.tokensSaved / 1_000_000 * 3).toFixed(2);
883
+ const ctxWindow = usage?.contextWindow ?? 0;
884
+ const daysExtended = ctxWindow > 0 && repo.tokensSaved > 0
885
+ ? (repo.tokensSaved / ctxWindow).toFixed(1)
886
+ : "0";
887
+ const costStr = `β‰ˆ $${usd} saved Β· ${daysExtended} context-windows extended`;
888
+ // Recall-quality badge (Phase 4): trust score from monitoring metrics.
889
+ const m = loadMetrics(currentStateDir);
890
+ const fp = fpRate(m, "L2");
891
+ const p95L2 = p95(m.latency.L2 ?? []);
892
+ const relPct = (st.dedupHitRate * 100).toFixed(0);
893
+ const qualityStr = `recall ${relPct}% relevant Β· FP ${(fp * 100).toFixed(1)}% Β· L2 p95 ${p95L2.toFixed(0)}ms`;
809
894
  ctx.ui.notify(
810
895
  `[mega-compact] pct=${pct} tokens=${tokens} tier=${config.tier} fastGate=${config.fastGatePct}% ` +
811
896
  `threshold=${config.thresholdTokens} auto=${config.auto} autoInline=${config.autoInline}\n` +
@@ -818,11 +903,97 @@ export default function (pi: ExtensionAPI) {
818
903
  `(${fmtB(di.compressedOriginalBytes)} compressed-original) Β· ` +
819
904
  `${di.duplicatesCollapsed} dedup-duplicates collapsed Β· ` +
820
905
  `${C.green}0 bytes permanently deleted${C.reset}\n` +
906
+ `[mega-compact] πŸ’° ${costStr}\n` +
907
+ `[mega-compact] 🎯 ${qualityStr}\n` +
821
908
  `[mega-compact] stateDir=${currentStateDir}`,
822
909
  );
823
910
  },
824
911
  });
825
912
 
913
+ // ---- Phase 4: cheap standout commands (data is already persisted) -------
914
+
915
+ /** Resolve a checkpoint by id (or "recent"/"last") from this session's store. */
916
+ function findCheckpoint(sid: string, ref: string) {
917
+ const all = listCheckpoints(sid, currentStateDir);
918
+ if (all.length === 0) return undefined;
919
+ if (!ref || ref === "recent" || ref === "last") return all[all.length - 1];
920
+ return all.find((c) => c.checkpointId === ref) ?? all.find((c) => c.checkpointId.endsWith(ref));
921
+ }
922
+
923
+ pi.registerCommand("mega-restore", {
924
+ description: "Re-inject a checkpoint's verbatim original region into context. Usage: /mega-restore <chkpt|recent>",
925
+ handler: async (args: string, ctx: ExtensionContext) => {
926
+ bindRepo(ctx.cwd);
927
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
928
+ const cp = findCheckpoint(sid, args.trim());
929
+ if (!cp) {
930
+ ctx.ui.notify(`[mega-compact] no checkpoint found${args.trim() ? ` for "${args.trim()}"` : ""} in this session. Try /mega-history.`);
931
+ return;
932
+ }
933
+ if (!cp.compressedOriginal) {
934
+ ctx.ui.notify(`[mega-compact] ${cp.checkpointId} has no recoverable original (pre-blob or direct add). Cannot restore verbatim.`);
935
+ return;
936
+ }
937
+ const original = decompressSmart(cp.compressedOriginal).toString("utf-8");
938
+ // Re-inject verbatim via before_agent_start (PREVENT-PI-003) β€” never
939
+ // touches live messages, only prepends the restored region to systemPrompt.
940
+ pendingRecallBlock = `The following compacted context was RESTORED from checkpoint ${cp.checkpointId} (verbatim original region):\n\n${original}`;
941
+ const files = cp.filesModified?.length ? cp.filesModified.join(", ") : "(no files captured)";
942
+ ctx.ui.notify(
943
+ `[mega-compact] β™» restored ${cp.checkpointId} β€” ${original.length} chars re-injected on next turn.\n` +
944
+ `[mega-compact] files: ${files}`,
945
+ );
946
+ dashboard.event("restore", { checkpointId: cp.checkpointId, chars: original.length });
947
+ },
948
+ });
949
+
950
+ pi.registerCommand("mega-history", {
951
+ description: "List this session's checkpoints (id, date, files, tokens). Usage: /mega-history",
952
+ handler: async (_args: string, ctx: ExtensionContext) => {
953
+ bindRepo(ctx.cwd);
954
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
955
+ const all = listCheckpoints(sid, currentStateDir);
956
+ if (all.length === 0) {
957
+ ctx.ui.notify("[mega-compact] no checkpoints in this session yet.");
958
+ return;
959
+ }
960
+ const rows = all.map((c) => {
961
+ const when = c.timestamp ? new Date(c.timestamp).toISOString().slice(0, 16).replace("T", " ") : "β€”";
962
+ const files = c.filesModified?.length ? c.filesModified.map((f) => f.split("/").pop()).join(", ") : "β€”";
963
+ const orig = c.originalTokenEstimate ?? 0;
964
+ const stored = c.tokenEstimate ?? 0;
965
+ const saved = Math.max(0, orig - stored);
966
+ return ` ${c.checkpointId} ${when} ${C.cyan}${saved}t saved${C.reset} ${files}`;
967
+ });
968
+ ctx.ui.notify(
969
+ `[mega-compact] ${all.length} checkpoint(s) in this session:\n` + rows.join("\n") +
970
+ `\n[mega-compact] /mega-view <chkpt> to see the original region Β· /mega-restore <chkpt> to re-inject it`,
971
+ );
972
+ },
973
+ });
974
+
975
+ pi.registerCommand("mega-view", {
976
+ description: "Show a checkpoint's verbatim original region. Usage: /mega-view <chkpt|recent>",
977
+ handler: async (args: string, ctx: ExtensionContext) => {
978
+ bindRepo(ctx.cwd);
979
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
980
+ const cp = findCheckpoint(sid, args.trim());
981
+ if (!cp) {
982
+ ctx.ui.notify(`[mega-compact] no checkpoint found${args.trim() ? ` for "${args.trim()}"` : ""}. Try /mega-history.`);
983
+ return;
984
+ }
985
+ if (!cp.compressedOriginal) {
986
+ ctx.ui.notify(`[mega-compact] ${cp.checkpointId} summary:\n${cp.summary.slice(0, 500)}${cp.summary.length > 500 ? "…" : ""}\n(no verbatim original stored)`);
987
+ return;
988
+ }
989
+ const original = decompressSmart(cp.compressedOriginal).toString("utf-8");
990
+ ctx.ui.notify(
991
+ `[mega-compact] ${cp.checkpointId} β€” original region (${original.length} chars):\n` +
992
+ `${original.slice(0, 1500)}${original.length > 1500 ? "\n…(truncated)" : ""}`,
993
+ );
994
+ },
995
+ });
996
+
826
997
  pi.registerCommand("mega-tier", {
827
998
  description: "Show or change the compaction tier at runtime. Usage: /mega-tier [low|medium|high|ultra|mega]",
828
999
  handler: async (args: string, ctx: ExtensionContext) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
4
4
  "description": "Layered, local, vector-backed context compressor for pi β€” supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-2-Clause",
@@ -0,0 +1,58 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { VectorStore } from "../vectorStore.js";
7
+ import { listCheckpoints, dataInvariantStats } from "./sqlite.js";
8
+ import { decompressSmart, compressSmart } from "./compression.js";
9
+
10
+ const baseTmp = mkdtempSync(join(tmpdir(), "mc-p04-"));
11
+
12
+ let counter = 0;
13
+ function store(opts: { dedupSim?: number } = {}) {
14
+ const dir = join(baseTmp, `run-${counter++}`);
15
+ return { s: new VectorStore({ dedupSim: opts.dedupSim ?? 0.9, stateDir: dir }), dir };
16
+ }
17
+
18
+ test("Phase 4: added checkpoint is listed and its compressed-original round-trips", () => {
19
+ const { s, dir } = store();
20
+ const original = "the original region text that gets compacted and must be restorable verbatim";
21
+ const r = s.add({ sessionId: "sess_a", summary: "s", regionText: original, tokenEstimate: 5, originalTokenEstimate: 60, timestamp: 1 });
22
+ const all = listCheckpoints("sess_a", dir);
23
+ assert.equal(all.length, 1, "one checkpoint listed");
24
+ const cp = all[0];
25
+ assert.ok(cp.compressedOriginal, "compressed-original blob present");
26
+ // The DR/restore path: decompressSmart must return the exact original.
27
+ const restored = decompressSmart(cp.compressedOriginal!).toString("utf-8");
28
+ assert.equal(restored, original, "restored verbatim === original");
29
+ assert.equal(cp.checkpointId, r.checkpoint.checkpointId);
30
+ });
31
+
32
+ test("Phase 4: findCheckpoint-by-id resolves a listed checkpoint", () => {
33
+ const { s, dir } = store();
34
+ s.add({ sessionId: "sess_b", summary: "s1", regionText: "region one text content here", tokenEstimate: 4, originalTokenEstimate: 40, timestamp: 1 });
35
+ const r2 = s.add({ sessionId: "sess_b", summary: "s2", regionText: "region two text content here different", tokenEstimate: 4, originalTokenEstimate: 45, timestamp: 2 });
36
+ const all = listCheckpoints("sess_b", dir);
37
+ assert.equal(all.length, 2);
38
+ const wanted = all.find((c) => c.checkpointId === r2.checkpoint.checkpointId)!;
39
+ assert.ok(wanted, "checkpoint resolved by id");
40
+ assert.ok(wanted.compressedOriginal, "has restorable original");
41
+ });
42
+
43
+ test("Phase 4: dataInvariantStats sanity for restore trust (0 deleted)", () => {
44
+ const { s, dir } = store();
45
+ s.add({ sessionId: "sess_c", summary: "s", regionText: "retained region body text", tokenEstimate: 5, originalTokenEstimate: 50, timestamp: 1 });
46
+ const di = dataInvariantStats(dir);
47
+ assert.equal(di.regionsRetained, 1);
48
+ assert.ok(di.compressedOriginalBytes > 0);
49
+ assert.equal(di.bytesPermanentlyDeleted, 0);
50
+ });
51
+
52
+ test("Phase 4: compressSmart/decompressSmart round-trips arbitrary text", () => {
53
+ const text = "x".repeat(2000);
54
+ const back = decompressSmart(compressSmart(Buffer.from(text, "utf-8"))).toString("utf-8");
55
+ assert.equal(back, text);
56
+ });
57
+
58
+ process.on("exit", () => { try { rmSync(baseTmp, { recursive: true, force: true }); } catch { /* ignore */ } });