pi-mega-compact 0.7.1 → 0.7.3
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.
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { compactSession } from "../src/engine.js";
|
|
11
|
-
import { recallAndInline, recallAndInlineAsync, formatRecallBlock
|
|
11
|
+
import { recallAndInline, recallAndInlineAsync, formatRecallBlock } from "../src/recall.js";
|
|
12
12
|
import { normalizeSessionId } from "../src/store.js";
|
|
13
13
|
import { estimateBlockTokens } from "../src/tokens.js";
|
|
14
14
|
import { touchSession, logDaily } from "../src/store/sqlite.js";
|
|
15
15
|
import { consolidateMemories } from "../src/memory.js";
|
|
16
|
-
import { C, MARKER_TYPE } from "./mega-runtime.js";
|
|
17
|
-
import { resolveRepoRoot, preserveRecentForPressure
|
|
16
|
+
import { C, MARKER_TYPE, } from "./mega-runtime.js";
|
|
17
|
+
import { resolveRepoRoot, preserveRecentForPressure } from "./mega-config.js";
|
|
18
18
|
import { runRaptor } from "../src/dedup/raptor/index.js";
|
|
19
19
|
import { loadDedupConfig } from "../src/config/dedup.js";
|
|
20
20
|
import { upsertEmbedding as indexUpsertEmbedding } from "../src/store/vectorIndex.js";
|
|
@@ -110,17 +110,13 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
|
|
|
110
110
|
// Grow the rolling "saved" goal so the progress bar always has a fresh
|
|
111
111
|
// denominator (we don't want it pinned at 100% once we pass an old target).
|
|
112
112
|
if (runtime.rt.tokensSaved > runtime.savedGoal)
|
|
113
|
-
runtime.savedGoal =
|
|
114
|
-
Math.ceil((runtime.rt.tokensSaved * 1.25) / 10_000) * 10_000;
|
|
113
|
+
runtime.savedGoal = Math.ceil((runtime.rt.tokensSaved * 1.25) / 10_000) * 10_000;
|
|
115
114
|
// Live toolbar activity: what file/region just got compacted or deduped.
|
|
116
115
|
// Rendered via the rotating ticker line (see snapshot); the ring buffer is
|
|
117
116
|
// cycled one-per-repaint so the single line scrolls through recent files.
|
|
118
117
|
const files = result.filesModified ?? [];
|
|
119
118
|
const fileLabel = files.length
|
|
120
|
-
? files
|
|
121
|
-
.map((f) => f.split("/").pop() ?? f)
|
|
122
|
-
.slice(0, 2)
|
|
123
|
-
.join(", ")
|
|
119
|
+
? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ")
|
|
124
120
|
: result.regionHash.slice(0, 8);
|
|
125
121
|
runtime.lastActivityAt = Date.now();
|
|
126
122
|
// Explain-why line: surfaced while fresh. Pulls the dedup reason (which for
|
|
@@ -172,10 +168,7 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
|
|
|
172
168
|
// waiting for the next turn-cadence tick. Uses the shared runMemoryReview
|
|
173
169
|
// helper (fire-and-forget; doCompact is sync). Best-effort + non-fatal. Only
|
|
174
170
|
// fires above the `high` band so low-pressure compactions don't pay the cost.
|
|
175
|
-
if (!result.deduped &&
|
|
176
|
-
config.memoryAutoReview &&
|
|
177
|
-
runtime.pressureBand !== "low" &&
|
|
178
|
-
runtime.pressureBand !== "medium") {
|
|
171
|
+
if (!result.deduped && config.memoryAutoReview && runtime.pressureBand !== "low" && runtime.pressureBand !== "medium") {
|
|
179
172
|
void runMemoryReview(runtime, view, "pressure");
|
|
180
173
|
}
|
|
181
174
|
// Sentinel marker: a non-LLM bookkeeping entry so subsequent triggers can
|
|
@@ -203,9 +196,7 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
|
|
|
203
196
|
// S25: stamp the tree with the newest checkpoint epoch so the
|
|
204
197
|
// freshness guard in raptorSearchHits can reject stale trees after a
|
|
205
198
|
// later compaction adds newer checkpoints.
|
|
206
|
-
const builtAt = all.length > 0
|
|
207
|
-
? Math.max(...all.map((c) => c.timestamp))
|
|
208
|
-
: Date.now();
|
|
199
|
+
const builtAt = all.length > 0 ? Math.max(...all.map((c) => c.timestamp)) : Date.now();
|
|
209
200
|
runRaptor(leaves, {
|
|
210
201
|
stateDir: runtime.currentStateDir,
|
|
211
202
|
sessionId: sid,
|
|
@@ -323,8 +314,7 @@ export function piCompactWouldNoop(ctx) {
|
|
|
323
314
|
if (m.role !== "toolResult")
|
|
324
315
|
isCut = true;
|
|
325
316
|
const c = m.content;
|
|
326
|
-
const text = typeof c === "string"
|
|
327
|
-
? c
|
|
317
|
+
const text = typeof c === "string" ? c
|
|
328
318
|
: Array.isArray(c)
|
|
329
319
|
? c.map((b) => b?.text ?? "").join(" ")
|
|
330
320
|
: "";
|
|
@@ -377,22 +367,12 @@ export function doRecall(runtime, config, ctx, query, source) {
|
|
|
377
367
|
liveWindow,
|
|
378
368
|
dedupSim: config.dedupSim,
|
|
379
369
|
}, runtime.store);
|
|
380
|
-
runtime.dashboard.event("recall", {
|
|
381
|
-
source,
|
|
382
|
-
query: query.slice(0, 120),
|
|
383
|
-
injected: result.toInject.length,
|
|
384
|
-
empty: result.empty,
|
|
385
|
-
});
|
|
370
|
+
runtime.dashboard.event("recall", { source, query: query.slice(0, 120), injected: result.toInject.length, empty: result.empty });
|
|
386
371
|
if (!result.empty && result.toInject.length > 0) {
|
|
387
372
|
const top = result.toInject[0];
|
|
388
373
|
const scorePct = Math.round((top.score ?? 0) * 100);
|
|
389
374
|
const files = top.checkpoint.filesModified ?? [];
|
|
390
|
-
const label = files.length
|
|
391
|
-
? files
|
|
392
|
-
.map((f) => f.split("/").pop() ?? f)
|
|
393
|
-
.slice(0, 2)
|
|
394
|
-
.join(", ")
|
|
395
|
-
: top.checkpoint.checkpointId;
|
|
375
|
+
const label = files.length ? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ") : top.checkpoint.checkpointId;
|
|
396
376
|
runtime.pushTicker(`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} · ${scorePct}% · ${label}`);
|
|
397
377
|
runtime.lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
|
|
398
378
|
}
|
|
@@ -415,15 +395,9 @@ export async function doRecallAsync(runtime, config, ctx, query, source, opts =
|
|
|
415
395
|
const liveWindow = config.windowDedupe ? extractLiveWindow(ctx) : undefined;
|
|
416
396
|
// Sync same-repo first (fast, never blocks).
|
|
417
397
|
const sameRepo = recallAndInline({
|
|
418
|
-
sessionId: sid,
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
source,
|
|
422
|
-
skipInjected: true,
|
|
423
|
-
recallMaxTokens: config.recallMaxTokens,
|
|
424
|
-
windowDedupe: config.windowDedupe,
|
|
425
|
-
liveWindow,
|
|
426
|
-
dedupSim: config.dedupSim,
|
|
398
|
+
sessionId: sid, query, limit: config.autoInlineK, source, skipInjected: true,
|
|
399
|
+
recallMaxTokens: config.recallMaxTokens, windowDedupe: config.windowDedupe,
|
|
400
|
+
liveWindow, dedupSim: config.dedupSim,
|
|
427
401
|
}, runtime.store);
|
|
428
402
|
if (!config.crossRepoEnabled || !opts.crossRepo)
|
|
429
403
|
return sameRepo;
|
|
@@ -432,22 +406,13 @@ export async function doRecallAsync(runtime, config, ctx, query, source, opts =
|
|
|
432
406
|
// Augment: cross-repo HNSW (async) with the stricter floor. Non-fatal.
|
|
433
407
|
try {
|
|
434
408
|
const x = await recallAndInlineAsync({
|
|
435
|
-
sessionId: sid,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
source,
|
|
439
|
-
skipInjected: true,
|
|
440
|
-
recallMaxTokens: config.recallMaxTokens,
|
|
441
|
-
windowDedupe: config.windowDedupe,
|
|
442
|
-
liveWindow,
|
|
443
|
-
dedupSim: config.crossRepoCosine,
|
|
444
|
-
crossRepo: true,
|
|
409
|
+
sessionId: sid, query, limit: config.autoInlineK, source, skipInjected: true,
|
|
410
|
+
recallMaxTokens: config.recallMaxTokens, windowDedupe: config.windowDedupe,
|
|
411
|
+
liveWindow, dedupSim: config.crossRepoCosine, crossRepo: true,
|
|
445
412
|
globalIndexDir: process.env.MEGACOMPACT_INDEX_DIR,
|
|
446
413
|
}, runtime.store);
|
|
447
414
|
runtime.dashboard.event("recall-crossrepo", {
|
|
448
|
-
source,
|
|
449
|
-
query: query.slice(0, 120),
|
|
450
|
-
injected: x.toInject.length,
|
|
415
|
+
source, query: query.slice(0, 120), injected: x.toInject.length,
|
|
451
416
|
sourceRepos: x.toInject.map((h) => h.repoId).filter(Boolean),
|
|
452
417
|
});
|
|
453
418
|
// Merge, dedup by checkpointId, respect the same token cap by reformatting.
|
|
@@ -93,6 +93,33 @@ function visibleWidth(s) {
|
|
|
93
93
|
return w;
|
|
94
94
|
}
|
|
95
95
|
/** Pad a content string (with ANSI colors) to `width` cells using panel bg. */
|
|
96
|
+
/** Wrap a string (with ANSI codes) to fit within `maxWidth` visible chars.
|
|
97
|
+
* Splits at │ separators or whitespace when possible. */
|
|
98
|
+
function wrapLine(text, maxWidth) {
|
|
99
|
+
if (maxWidth <= 0)
|
|
100
|
+
return [text];
|
|
101
|
+
const result = [];
|
|
102
|
+
let current = "";
|
|
103
|
+
let currentW = 0;
|
|
104
|
+
// Split at │ boundaries first
|
|
105
|
+
const segments = text.split("│");
|
|
106
|
+
for (let i = 0; i < segments.length; i++) {
|
|
107
|
+
const seg = (i > 0 ? "│" : "") + segments[i];
|
|
108
|
+
const segW = visibleWidth(PANEL_BG + seg.replace(/\x1b\[0m/g, PANEL_RST));
|
|
109
|
+
if (currentW + segW <= maxWidth || currentW === 0) {
|
|
110
|
+
current += seg;
|
|
111
|
+
currentW += segW;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
result.push(current);
|
|
115
|
+
current = seg;
|
|
116
|
+
currentW = segW;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (current)
|
|
120
|
+
result.push(current);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
96
123
|
function panelLine(content, width) {
|
|
97
124
|
const withBg = PANEL_BG + content.replace(/\x1b\[0m/g, PANEL_RST);
|
|
98
125
|
const pad = Math.max(0, width - visibleWidth(withBg));
|
|
@@ -603,15 +630,26 @@ export class MegaRuntime {
|
|
|
603
630
|
const pulse = wd.pulsing
|
|
604
631
|
? `${C.cyan}${PULSE[Math.floor(Date.now() / 250) % PULSE.length]}${C.reset} `
|
|
605
632
|
: "";
|
|
633
|
+
const sep = ` ${C.dim}│${C.reset} `;
|
|
634
|
+
// Build one long content line — let terminal wrap it naturally
|
|
635
|
+
const content = [
|
|
636
|
+
`${C.amber}⚡ ${wd.tierLabel}${C.reset} v${C.bold}${wd.version}${C.reset} ${ramp(wd.ctxPct, 20)} ${C.bold}${wd.pctStr}${C.reset} ${wd.tokStr}/${wd.maxStr}`,
|
|
637
|
+
wd.triggerLabel,
|
|
638
|
+
`${C.cyan}${wd.modelStr}${C.reset}`,
|
|
639
|
+
`${wd.chk} chk${wd.agentStr}${wd.turnStr}`,
|
|
640
|
+
`${C.magenta}dup ${wd.dedupStr}${C.reset}`,
|
|
641
|
+
`${C.gray}sess${C.reset} ${fmtTokens(wd.sessIn)}→${fmtTokens(wd.sessKept)} kept ${C.green}(${wd.sTxt}% freed)${C.reset}`,
|
|
642
|
+
`${C.gray}all-time${C.reset} ${fmtTokens(wd.repoIn)}→${fmtTokens(wd.repoKept)} kept ${C.blue}(${wd.rTxt}% freed)${C.reset}`,
|
|
643
|
+
`${wd.repoChk} chk/${wd.repoSess} sess`,
|
|
644
|
+
`${C.gray}mem${C.reset} ${wd.embedderName} · ${wd.chk} chunks · ${C.blue}comp ${wd.compStr}${C.reset}`,
|
|
645
|
+
`${C.gray}drift${C.reset} ${wd.driftStatus === "ok" ? C.green : C.amber}${wd.driftStatus}${C.reset}`,
|
|
646
|
+
`${C.gray}compact${C.reset} ${sinceCompactStr(wd.sinceCompact)}`,
|
|
647
|
+
].join(sep);
|
|
648
|
+
// Wrap to terminal width and pad each line
|
|
649
|
+
const wrapped = wrapLine(content, width - 2); // 2-char indent
|
|
606
650
|
const lines = [
|
|
607
|
-
// top border
|
|
608
651
|
panelBar(width, "─"),
|
|
609
|
-
|
|
610
|
-
panelLine(` ${C.amber}⚡ ${wd.tierLabel}${C.reset} v${C.bold}${wd.version}${C.reset} ${ramp(wd.ctxPct, 20)} ${C.bold}${wd.pctStr}${C.reset} ${wd.tokStr}/${wd.maxStr} │ ${wd.triggerLabel} │ ${C.cyan}${wd.modelStr}${C.reset} │ ${wd.chk} chk${wd.agentStr}${wd.turnStr}`, width),
|
|
611
|
-
// L2 — savings reconciled (session + all-time)
|
|
612
|
-
panelLine(` ${C.magenta}dup ${wd.dedupStr}${C.reset} │ ${C.gray}sess${C.reset} ${fmtTokens(wd.sessIn)}→${fmtTokens(wd.sessKept)} kept ${C.green}(${wd.sTxt}% freed)${C.reset} · ${C.gray}all-time${C.reset} ${fmtTokens(wd.repoIn)}→${fmtTokens(wd.repoKept)} kept ${C.blue}(${wd.rTxt}% freed)${C.reset} │ ${wd.repoChk} chk/${wd.repoSess} sess`, width),
|
|
613
|
-
// L3 — memory store + compression + drift + since-compact (NEW)
|
|
614
|
-
panelLine(` ${C.gray}mem${C.reset} ${wd.embedderName} · ${wd.chk} chunks · ${C.blue}comp ${wd.compStr}${C.reset} │ ${C.gray}drift${C.reset} ${wd.driftStatus === "ok" ? C.green : C.amber}${wd.driftStatus}${C.reset} │ ${C.gray}compact${C.reset} ${sinceCompactStr(wd.sinceCompact)}`, width),
|
|
652
|
+
...wrapped.map((l) => panelLine(l, width)),
|
|
615
653
|
];
|
|
616
654
|
// L4 — agents block (S27, count + status; per-agent tokens gated on P0)
|
|
617
655
|
if (wd.agentsActive) {
|
|
@@ -30,13 +30,13 @@ import type { MegaConfig } from "./mega-config.js";
|
|
|
30
30
|
import { recallRaptorRootSummary } from "../src/dedup/raptor/index.js";
|
|
31
31
|
|
|
32
32
|
export interface NativeCompactionResult {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
/** Our trimmed summary + the pi entry to keep from (durable trim). */
|
|
34
|
+
compaction: {
|
|
35
|
+
summary: string;
|
|
36
|
+
firstKeptEntryId: string;
|
|
37
|
+
tokensBefore: number;
|
|
38
|
+
estimatedTokensAfter: number;
|
|
39
|
+
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -46,61 +46,61 @@ export interface NativeCompactionResult {
|
|
|
46
46
|
* own native compaction, or skip). Never throws for "empty" — best-effort.
|
|
47
47
|
*/
|
|
48
48
|
export function driveNativeCompaction(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
event: SessionBeforeCompactEvent,
|
|
50
|
+
runtime: MegaRuntime,
|
|
51
|
+
config: MegaConfig,
|
|
52
52
|
): NativeCompactionResult | undefined {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const prep = event.preparation;
|
|
54
|
+
if (!prep) return undefined;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
const sid = runtime.rt.sessionId;
|
|
57
|
+
const messagesToSummarize: AgentMessage[] = prep.messagesToSummarize ?? [];
|
|
58
|
+
if (messagesToSummarize.length === 0) return undefined;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const engineView = toEngineMessages(messagesToSummarize);
|
|
61
|
+
// We don't drop anything here — pi keeps from prep.firstKeptEntryId. We only
|
|
62
|
+
// summarize the region pi is about to discard.
|
|
63
|
+
const keepFrom = engineView.length;
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
const result = compactSession(
|
|
66
|
+
{
|
|
67
|
+
sessionId: sid,
|
|
68
|
+
messages: engineView,
|
|
69
|
+
keepFrom,
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
useExtractiveSummary: true,
|
|
72
|
+
},
|
|
73
|
+
runtime.store,
|
|
74
|
+
);
|
|
75
|
+
if (result.skipped) return undefined;
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
// Prefer the RAPTOR root summary when the tree is built + enabled (Fix D):
|
|
78
|
+
// it is a session-level compressed summary, broader than one slice's. Fall
|
|
79
|
+
// back to the extractive topicSummary of this slice.
|
|
80
|
+
let summary = result.summary;
|
|
81
|
+
if (config.raptorEnabled) {
|
|
82
|
+
const root = recallRaptorRootSummary(sid, runtime.currentStateDir);
|
|
83
|
+
if (root) summary = root;
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
const tokensBefore = prep.tokensBefore ?? estimateSessionTokens(engineView);
|
|
87
|
+
const summaryTokens = estimateBlockTokens(summary);
|
|
88
|
+
// pi keeps the tail from firstKeptEntryId; our summary replaces the discarded
|
|
89
|
+
// region. Honest saved = discarded-region tokens − our summary tokens.
|
|
90
|
+
const savedTokens = Math.max(0, tokensBefore - summaryTokens);
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
runtime.rt.lastCompactedFrom = keepFrom;
|
|
93
|
+
runtime.rt.lastCompactedTokens = tokensBefore;
|
|
94
|
+
runtime.rt.tokensSaved += savedTokens;
|
|
95
|
+
runtime.rt.lastCompactAt = Date.now();
|
|
96
|
+
runtime.rt.persistedThisSession = true;
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
return {
|
|
99
|
+
compaction: {
|
|
100
|
+
summary,
|
|
101
|
+
firstKeptEntryId: prep.firstKeptEntryId,
|
|
102
|
+
tokensBefore,
|
|
103
|
+
estimatedTokensAfter: summaryTokens,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
106
|
}
|