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.
- package/dist/extensions/mega-compact.js +170 -11
- package/dist/src/store/phase04.test.js +54 -0
- package/extensions/mega-compact.ts +180 -9
- package/package.json +1 -1
- package/src/store/phase04.test.ts +58 -0
|
@@ -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${
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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${
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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.
|
|
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 */ } });
|