pi-mega-compact 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/extensions/dashboard-server.js +112 -14
- package/dist/extensions/mega-commands.js +12 -1
- package/dist/extensions/mega-compact.test.js +286 -51
- package/dist/extensions/mega-config.js +67 -5
- package/dist/extensions/mega-events.js +151 -27
- package/dist/extensions/mega-runtime.js +163 -32
- package/dist/extensions/openclaw-mega-compact.js +291 -0
- package/dist/src/compact.js +5 -1
- package/dist/src/dedup-engine.test.js +63 -38
- package/dist/src/minilm.js +92 -0
- package/dist/src/supersede.js +8 -5
- package/dist/src/wordpiece.js +129 -0
- package/extensions/dashboard-server.ts +125 -14
- package/extensions/mega-commands.ts +12 -1
- package/extensions/mega-compact.test.ts +947 -516
- package/extensions/mega-config.ts +84 -6
- package/extensions/mega-dashboard.ts +11 -0
- package/extensions/mega-events.ts +558 -360
- package/extensions/mega-runtime.ts +168 -32
- package/package.json +1 -1
- package/src/compact.ts +5 -1
- package/src/dedup-engine.test.ts +103 -42
- package/src/supersede.ts +8 -5
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wordpiece.ts — a self-contained WordPiece tokenizer for BERT/MiniLM.
|
|
3
|
+
*
|
|
4
|
+
* Loads the canonical `vocab.txt` (bert-base-uncased, ~30K tokens) from disk and
|
|
5
|
+
* implements the standard uncased BERT preprocessing + greedy longest-match
|
|
6
|
+
* WordPiece segmentation. No native dependency, no network — the vocab file is a
|
|
7
|
+
* local artifact fetched once by scripts/setup-minilm.mjs (PREVENT-PI-004).
|
|
8
|
+
*
|
|
9
|
+
* This mirrors HuggingFace `BertTokenizer` closely enough for sentence-embedding
|
|
10
|
+
* use: lowercase, strip accents, split on whitespace + punctuation, then
|
|
11
|
+
* WordPiece each token with the `##` continuation convention. Special tokens
|
|
12
|
+
* [CLS]/[SEP] are added by the caller's encode().
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
15
|
+
const UNK = "[UNK]";
|
|
16
|
+
const CLS = "[CLS]";
|
|
17
|
+
const SEP = "[SEP]";
|
|
18
|
+
const PAD = "[PAD]";
|
|
19
|
+
const MAX_INPUT_CHARS_PER_WORD = 200;
|
|
20
|
+
export class WordPieceTokenizer {
|
|
21
|
+
vocab;
|
|
22
|
+
clsId;
|
|
23
|
+
sepId;
|
|
24
|
+
padId;
|
|
25
|
+
unkId;
|
|
26
|
+
constructor(vocab) {
|
|
27
|
+
this.vocab = vocab;
|
|
28
|
+
this.clsId = vocab.get(CLS) ?? 101;
|
|
29
|
+
this.sepId = vocab.get(SEP) ?? 102;
|
|
30
|
+
this.padId = vocab.get(PAD) ?? 0;
|
|
31
|
+
this.unkId = vocab.get(UNK) ?? 100;
|
|
32
|
+
}
|
|
33
|
+
/** Build a tokenizer from a vocab.txt file (one token per line, index = line). */
|
|
34
|
+
static fromVocabFile(path) {
|
|
35
|
+
if (!existsSync(path)) {
|
|
36
|
+
throw new Error(`WordPiece vocab not found at ${path}. Run: node scripts/setup-minilm.mjs`);
|
|
37
|
+
}
|
|
38
|
+
const lines = readFileSync(path, "utf-8").split("\n");
|
|
39
|
+
const vocab = new Map();
|
|
40
|
+
for (let i = 0; i < lines.length; i++) {
|
|
41
|
+
const tok = lines[i].replace(/\r$/, "");
|
|
42
|
+
if (tok.length > 0 || i < lines.length - 1)
|
|
43
|
+
vocab.set(tok, i);
|
|
44
|
+
}
|
|
45
|
+
return new WordPieceTokenizer(vocab);
|
|
46
|
+
}
|
|
47
|
+
/** Uncased BERT basic tokenization: lowercase, strip accents, split on ws+punct. */
|
|
48
|
+
basicTokenize(text) {
|
|
49
|
+
// NFD + strip combining marks (accent removal), then lowercase.
|
|
50
|
+
const cleaned = text
|
|
51
|
+
.normalize("NFD")
|
|
52
|
+
.replace(/[̀-ͯ]/g, "")
|
|
53
|
+
.toLowerCase();
|
|
54
|
+
const tokens = [];
|
|
55
|
+
let buf = "";
|
|
56
|
+
const flush = () => {
|
|
57
|
+
if (buf.length > 0) {
|
|
58
|
+
tokens.push(buf);
|
|
59
|
+
buf = "";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
for (const ch of cleaned) {
|
|
63
|
+
if (/\s/.test(ch)) {
|
|
64
|
+
flush();
|
|
65
|
+
}
|
|
66
|
+
else if (/[!-/:-@[-`{-~¡-¿]/.test(ch)) {
|
|
67
|
+
// Punctuation becomes its own token.
|
|
68
|
+
flush();
|
|
69
|
+
tokens.push(ch);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
buf += ch;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
flush();
|
|
76
|
+
return tokens;
|
|
77
|
+
}
|
|
78
|
+
/** Greedy longest-match WordPiece for a single word. */
|
|
79
|
+
wordpiece(word) {
|
|
80
|
+
if (word.length > MAX_INPUT_CHARS_PER_WORD)
|
|
81
|
+
return [UNK];
|
|
82
|
+
const pieces = [];
|
|
83
|
+
let start = 0;
|
|
84
|
+
while (start < word.length) {
|
|
85
|
+
let end = word.length;
|
|
86
|
+
let cur = null;
|
|
87
|
+
while (start < end) {
|
|
88
|
+
let sub = word.slice(start, end);
|
|
89
|
+
if (start > 0)
|
|
90
|
+
sub = "##" + sub;
|
|
91
|
+
if (this.vocab.has(sub)) {
|
|
92
|
+
cur = sub;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
end--;
|
|
96
|
+
}
|
|
97
|
+
if (cur === null)
|
|
98
|
+
return [UNK]; // any unmatchable piece → whole word is UNK
|
|
99
|
+
pieces.push(cur);
|
|
100
|
+
start = end;
|
|
101
|
+
}
|
|
102
|
+
return pieces;
|
|
103
|
+
}
|
|
104
|
+
/** Tokenize text into WordPiece token strings (no special tokens). */
|
|
105
|
+
tokenize(text) {
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const word of this.basicTokenize(text)) {
|
|
108
|
+
for (const piece of this.wordpiece(word))
|
|
109
|
+
out.push(piece);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Encode text into model inputs with [CLS]…[SEP], truncated to `maxLen`.
|
|
115
|
+
* attention_mask is all 1s (no padding for single-sequence inference).
|
|
116
|
+
*/
|
|
117
|
+
encode(text, maxLen = 256) {
|
|
118
|
+
const pieces = this.tokenize(text).slice(0, Math.max(0, maxLen - 2));
|
|
119
|
+
const inputIds = [this.clsId];
|
|
120
|
+
for (const p of pieces)
|
|
121
|
+
inputIds.push(this.vocab.get(p) ?? this.unkId);
|
|
122
|
+
inputIds.push(this.sepId);
|
|
123
|
+
return {
|
|
124
|
+
inputIds,
|
|
125
|
+
attentionMask: inputIds.map(() => 1),
|
|
126
|
+
tokenTypeIds: inputIds.map(() => 0),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -70,6 +70,17 @@ interface IndexRepo {
|
|
|
70
70
|
inputRate: number | null;
|
|
71
71
|
outputRate: number | null;
|
|
72
72
|
lastSeen: number;
|
|
73
|
+
// Per-repo token + model detail (S25 dashboard enrichment), read directly
|
|
74
|
+
// from each repo's node:sqlite store at stateDir. tokensKept = Σ stored
|
|
75
|
+
// summary tokens ("out"); tokensDropped = Σ original region tokens ("in");
|
|
76
|
+
// sessions = distinct sessions with a checkpoint; contextWindow/maxTokens/
|
|
77
|
+
// reasoning come from the latest model_snapshots row for that repo.
|
|
78
|
+
tokensKept: number;
|
|
79
|
+
tokensDropped: number;
|
|
80
|
+
sessions: number;
|
|
81
|
+
contextWindow: number | null;
|
|
82
|
+
maxTokens: number | null;
|
|
83
|
+
reasoning: boolean | null;
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
interface IndexSummary {
|
|
@@ -107,7 +118,54 @@ function readIndex(): IndexIndex | null {
|
|
|
107
118
|
inputRate: (r.input_rate as number | null) ?? null,
|
|
108
119
|
outputRate: (r.output_rate as number | null) ?? null,
|
|
109
120
|
lastSeen: Number(r.last_seen ?? 0),
|
|
121
|
+
// Defaults — enriched below from each repo's own store.
|
|
122
|
+
tokensKept: 0,
|
|
123
|
+
tokensDropped: 0,
|
|
124
|
+
sessions: 0,
|
|
125
|
+
contextWindow: null,
|
|
126
|
+
maxTokens: null,
|
|
127
|
+
reasoning: null,
|
|
110
128
|
}));
|
|
129
|
+
// Enrich each repo with per-store token + model detail read directly via
|
|
130
|
+
// node:sqlite (same zero-dependency invariant as readIndex; no store graph
|
|
131
|
+
// import). Best-effort: a missing/corrupt store degrades to the defaults
|
|
132
|
+
// above so the dashboard never fails to render.
|
|
133
|
+
for (const repo of mapped) {
|
|
134
|
+
try {
|
|
135
|
+
const storePath = join(repo.stateDir, "sqlite.db");
|
|
136
|
+
if (existsSync(storePath)) {
|
|
137
|
+
const sdb = new DatabaseSync(storePath, { readOnly: true });
|
|
138
|
+
try {
|
|
139
|
+
const tok = sdb
|
|
140
|
+
.prepare(
|
|
141
|
+
`SELECT COALESCE(SUM(token_estimate),0) AS kept,
|
|
142
|
+
COALESCE(SUM(original_token_estimate),0) AS dropped,
|
|
143
|
+
COUNT(DISTINCT session_id) AS sess
|
|
144
|
+
FROM context_chunks WHERE dedup_status != 'removed'`,
|
|
145
|
+
)
|
|
146
|
+
.get() as { kept: number; dropped: number; sess: number };
|
|
147
|
+
repo.tokensKept = Number(tok.kept ?? 0);
|
|
148
|
+
repo.tokensDropped = Number(tok.dropped ?? 0);
|
|
149
|
+
repo.sessions = Number(tok.sess ?? 0);
|
|
150
|
+
const mrow = sdb
|
|
151
|
+
.prepare(
|
|
152
|
+
`SELECT context_window, max_tokens, reasoning
|
|
153
|
+
FROM model_snapshots ORDER BY captured_at DESC LIMIT 1`,
|
|
154
|
+
)
|
|
155
|
+
.get() as { context_window: number; max_tokens: number; reasoning: number } | undefined;
|
|
156
|
+
if (mrow) {
|
|
157
|
+
repo.contextWindow = Number(mrow.context_window ?? 0) || null;
|
|
158
|
+
repo.maxTokens = Number(mrow.max_tokens ?? 0) || null;
|
|
159
|
+
repo.reasoning = Number(mrow.reasoning ?? 0) === 1;
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
sdb.close();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
/* best-effort — keep the defaults */
|
|
167
|
+
}
|
|
168
|
+
}
|
|
111
169
|
// Defensive display hygiene (belt-and-suspenders — the real fix is that
|
|
112
170
|
// tests now isolate via MEGACOMPACT_INDEX_DIR): drop transient test/temp
|
|
113
171
|
// paths that should never have been real repos, and collapse duplicate
|
|
@@ -236,14 +294,15 @@ function readSnapshot(snapshotPath: string) {
|
|
|
236
294
|
tier: "unknown",
|
|
237
295
|
presetTier: "unknown",
|
|
238
296
|
pressure: 0,
|
|
239
|
-
config: { fastGatePct: 80, thresholdTokens: 100_000, anchorUserMessages: 1, preserveRecent: 2, auto: true, autoInlineK: 3 },
|
|
297
|
+
config: { fastGatePct: 80, thresholdTokens: 100_000, tierPct: null, effectiveThresholdPct: null, anchorUserMessages: 1, preserveRecent: 2, auto: true, autoInlineK: 3 },
|
|
240
298
|
session: { id: null, state: null, persistedThisSession: false, lastCheckpointId: null, lastCompactedFrom: 0 },
|
|
241
299
|
context: { tokens: null, percent: null, contextWindow: 0 },
|
|
242
|
-
trigger: { armed: false, ready: false, currentTokens: null, thresholdTokens: 100_000, fastGatePct: 80 },
|
|
300
|
+
trigger: { armed: false, ready: false, currentTokens: null, thresholdTokens: 100_000, fastGatePct: 80, tierPct: null, effectiveThresholdPct: null },
|
|
243
301
|
store: { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, injectedCount: 0, dedupHitRate: 0, storageDedupRate: 0, dedupCollapsed: 0 },
|
|
244
302
|
crew: { activeAgents: 0, currentTurn: 0 },
|
|
245
303
|
repo: { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, sessionCount: 0, dedupAttempts: 0, dedupCollapsed: 0, storageDedupRate: 0 },
|
|
246
304
|
integrity: { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 },
|
|
305
|
+
compression: { session: { tokensIn: 0, tokensOut: 0, tokensFreed: 0, compressionPct: 0, dedupPct: 0 }, repo: { tokensIn: 0, tokensOut: 0, tokensFreed: 0, compressionPct: 0, dedupPct: 0 } },
|
|
247
306
|
model: undefined,
|
|
248
307
|
} as Snapshot;
|
|
249
308
|
}
|
|
@@ -430,9 +489,9 @@ function dashboardHtml(tierName: string): string {
|
|
|
430
489
|
<div class="conf-grid">
|
|
431
490
|
<span class="label" title="Live pressure band — climbs low→mega as context fills the window.">Tier (live)</span><span class="value" id="cf-tier">${tierName}</span>
|
|
432
491
|
<span class="label" title="The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.">Preset</span><span class="value" id="cf-preset">—</span>
|
|
433
|
-
<span class="label" title="Live pressure = currentTokens /
|
|
434
|
-
<span class="label">Threshold</span><span class="value" id="cf-threshold">—</span>
|
|
435
|
-
<span class="label">Fast Gate</span><span class="value" id="cf-gate">—</span>
|
|
492
|
+
<span class="label" title="Live pressure = currentTokens / threshold — % of the model context window (threshold fires at the tier's % of window).">Pressure</span><span class="value" id="cf-pressure">—</span>
|
|
493
|
+
<span class="label" title="Compaction threshold = tierPct × model context window — mega-compact trims BELOW pi's native ~80% auto-compact for any model size.">Threshold</span><span class="value" id="cf-threshold">—</span>
|
|
494
|
+
<span class="label" title="Fast-gate arming floor — the live trim arms once context passes this % of the window.">Fast Gate</span><span class="value" id="cf-gate">—</span>
|
|
436
495
|
<span class="label">Auto</span><span class="value" id="cf-auto">—</span>
|
|
437
496
|
<span class="label">Anchor</span><span class="value" id="cf-anchor">—</span>
|
|
438
497
|
</div>
|
|
@@ -541,15 +600,23 @@ function dashboardHtml(tierName: string): string {
|
|
|
541
600
|
<thead>
|
|
542
601
|
<tr>
|
|
543
602
|
<th>Model</th><th>Provider</th>
|
|
544
|
-
<th style="text-align:right">
|
|
603
|
+
<th style="text-align:right" title="Tokens dropped from context by compaction (the input reclaimed)">Tokens In</th>
|
|
604
|
+
<th style="text-align:right" title="Tokens kept as compacted summaries still in context (the output retained)">Tokens Out</th>
|
|
605
|
+
<th style="text-align:right">Freed</th>
|
|
606
|
+
<th style="text-align:right" title="Model context window (max input tokens the model accepts)">Ctx Window</th>
|
|
607
|
+
<th style="text-align:right" title="Model max output tokens per turn">Max Out</th>
|
|
608
|
+
<th style="text-align:right" title="Reasoning-capable model">Reas.</th>
|
|
609
|
+
<th style="text-align:right" title="Distinct sessions with at least one checkpoint">Sessions</th>
|
|
545
610
|
<th style="text-align:right">Checkpoints</th>
|
|
546
|
-
<th style="text-align:right">
|
|
611
|
+
<th style="text-align:right" title="USD per input token">In $/tok</th>
|
|
612
|
+
<th style="text-align:right" title="USD per output token">Out $/tok</th>
|
|
547
613
|
<th style="text-align:right">$ Saved</th>
|
|
548
614
|
<th style="text-align:right">Last Used</th>
|
|
549
615
|
</tr>
|
|
550
616
|
</thead>
|
|
551
|
-
<tbody id="bm-rows"><tr><td colspan="
|
|
617
|
+
<tbody id="bm-rows"><tr><td colspan="14" class="repo-none">loading…</td></tr></tbody>
|
|
552
618
|
</table>
|
|
619
|
+
<p class="legend-note" style="margin-top:8px">Tokens In = Σ original region tokens dropped by compaction. Tokens Out = Σ compacted summary tokens still retained in context. Freed = Tokens In − Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come from the latest captured model snapshot for each repo.</p>
|
|
553
620
|
|
|
554
621
|
<div class="updated" id="sm-updated"></div>
|
|
555
622
|
</div>
|
|
@@ -645,7 +712,17 @@ function dashboardHtml(tierName: string): string {
|
|
|
645
712
|
document.getElementById('cf-tier').textContent = d.tier + ' (live)';
|
|
646
713
|
document.getElementById('cf-preset').textContent = d.presetTier;
|
|
647
714
|
document.getElementById('cf-pressure').textContent = Math.round((d.pressure || 0) * 100) + '%';
|
|
648
|
-
|
|
715
|
+
// (b) Threshold: show the effective token threshold AND the % of the model
|
|
716
|
+
// context window it represents (percentage-based tiers). d.config.tierPct
|
|
717
|
+
// is present on the live snapshot written by the runtime (Phase-1/2a).
|
|
718
|
+
var cfgPct = d.config.tierPct;
|
|
719
|
+
var cw = d.context.contextWindow || 0;
|
|
720
|
+
var thresholdTxt = d.config.thresholdTokens.toLocaleString();
|
|
721
|
+
if (cfgPct != null && cw > 0) {
|
|
722
|
+
thresholdTxt += ' (' + Math.round(cfgPct * 100) + '% of ' + cw.toLocaleString() + ')';
|
|
723
|
+
}
|
|
724
|
+
document.getElementById('cf-threshold').textContent = thresholdTxt;
|
|
725
|
+
// (c) Fast Gate: arming floor — live trim arms once context passes this %.
|
|
649
726
|
document.getElementById('cf-gate').textContent = d.config.fastGatePct + '%';
|
|
650
727
|
document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
|
|
651
728
|
document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
|
|
@@ -771,33 +848,67 @@ function dashboardHtml(tierName: string): string {
|
|
|
771
848
|
var rows = document.getElementById('bm-rows');
|
|
772
849
|
if (!rows) return;
|
|
773
850
|
if (!repos || !repos.length) {
|
|
774
|
-
rows.innerHTML = '<tr><td colspan="
|
|
851
|
+
rows.innerHTML = '<tr><td colspan="14" class="repo-none">No repositories registered yet.</td></tr>';
|
|
775
852
|
return;
|
|
776
853
|
}
|
|
777
854
|
var groups = {};
|
|
778
855
|
for (var i = 0; i < repos.length; i++) {
|
|
779
856
|
var r = repos[i];
|
|
780
857
|
var key = (r.modelName && String(r.modelName).trim()) || '(unknown)';
|
|
781
|
-
if (!groups[key]) groups[key] = {
|
|
858
|
+
if (!groups[key]) groups[key] = {
|
|
859
|
+
model: key, provider: r.providerName || r.provider || '—', repos: 0, checkpoints: 0,
|
|
860
|
+
tokensSaved: 0, tokensIn: 0, tokensOut: 0, sessions: 0, usd: 0, lastAt: 0,
|
|
861
|
+
inRates: [], outRates: [], ctxWindows: [], maxTokens: [], reasoning: null,
|
|
862
|
+
};
|
|
782
863
|
var g = groups[key];
|
|
783
864
|
g.repos++;
|
|
784
865
|
g.checkpoints += (r.checkpointCount || 0);
|
|
785
866
|
g.tokensSaved += (r.tokensSaved || 0);
|
|
786
|
-
|
|
867
|
+
g.tokensIn += (r.tokensDropped || 0);
|
|
868
|
+
g.tokensOut += (r.tokensKept || 0);
|
|
869
|
+
g.sessions += (r.sessions || 0);
|
|
870
|
+
if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.inRates.push(r.inputRate); }
|
|
871
|
+
if (r.outputRate) g.outRates.push(r.outputRate);
|
|
872
|
+
if (r.contextWindow) g.ctxWindows.push(r.contextWindow);
|
|
873
|
+
if (r.maxTokens) g.maxTokens.push(r.maxTokens);
|
|
874
|
+
if (r.reasoning != null) g.reasoning = r.reasoning;
|
|
787
875
|
if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt;
|
|
788
876
|
}
|
|
789
877
|
var arr = [];
|
|
790
878
|
for (var k in groups) { if (Object.prototype.hasOwnProperty.call(groups, k)) arr.push(groups[k]); }
|
|
791
879
|
arr.sort(function(a, b) { return b.tokensSaved - a.tokensSaved; });
|
|
880
|
+
// Helper: a set of numeric samples collapses to a single value when all
|
|
881
|
+
// repos in the group agree, otherwise shows the range (min–max) so the
|
|
882
|
+
// user can see mixed-config model groups at a glance.
|
|
883
|
+
function collapseNum(samples) {
|
|
884
|
+
if (!samples || !samples.length) return '—';
|
|
885
|
+
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
|
|
886
|
+
return lo === hi ? lo.toLocaleString() : lo.toLocaleString() + '–' + hi.toLocaleString();
|
|
887
|
+
}
|
|
888
|
+
function collapseRate(samples) {
|
|
889
|
+
if (!samples || !samples.length) return '—';
|
|
890
|
+
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
|
|
891
|
+
var fmt = function(v) { return '$' + v.toFixed(6); };
|
|
892
|
+
return lo === hi ? fmt(lo) : fmt(lo) + '–' + fmt(hi);
|
|
893
|
+
}
|
|
792
894
|
rows.innerHTML = arr.map(function(g) {
|
|
895
|
+
var freed = (g.tokensIn || 0) - (g.tokensOut || 0);
|
|
793
896
|
var usd = g.usd > 0 ? '$' + g.usd.toFixed(4) : '—';
|
|
794
897
|
var when = g.lastAt ? new Date(g.lastAt).toLocaleString() : '—';
|
|
898
|
+
var reas = g.reasoning == null ? '—' : (g.reasoning ? 'yes' : 'no');
|
|
795
899
|
return '<tr>' +
|
|
796
900
|
'<td><span class="repo-model">' + sanitize(g.model) + '</span></td>' +
|
|
797
901
|
'<td>' + sanitize(g.provider) + '</td>' +
|
|
798
|
-
'<td class="num">' + g.
|
|
902
|
+
'<td class="num">' + (g.tokensIn || 0).toLocaleString() + '</td>' +
|
|
903
|
+
'<td class="num">' + (g.tokensOut || 0).toLocaleString() + '</td>' +
|
|
904
|
+
'<td class="num">' + freed.toLocaleString() + '</td>' +
|
|
905
|
+
'<td class="num">' + collapseNum(g.ctxWindows) + '</td>' +
|
|
906
|
+
'<td class="num">' + collapseNum(g.maxTokens) + '</td>' +
|
|
907
|
+
'<td class="num">' + reas + '</td>' +
|
|
908
|
+
'<td class="num">' + g.sessions.toLocaleString() + '</td>' +
|
|
799
909
|
'<td class="num">' + g.checkpoints.toLocaleString() + '</td>' +
|
|
800
|
-
'<td class="num">' + g.
|
|
910
|
+
'<td class="num">' + collapseRate(g.inRates) + '</td>' +
|
|
911
|
+
'<td class="num">' + collapseRate(g.outRates) + '</td>' +
|
|
801
912
|
'<td class="num">' + sanitize(usd) + '</td>' +
|
|
802
913
|
'<td class="num">' + sanitize(when) + '</td>' +
|
|
803
914
|
'</tr>';
|
|
@@ -125,10 +125,21 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
125
125
|
repoCount = listRepoRegistry(process.env.MEGACOMPACT_INDEX_DIR).length;
|
|
126
126
|
} catch { /* non-fatal */ }
|
|
127
127
|
const crossRepoStr = `${crossRepoInjections} cross-repo injections recorded · ${repoCount} repos indexed`;
|
|
128
|
+
// Effective compaction threshold = tierPct × model context window (kept
|
|
129
|
+
// BELOW pi's native ~80% auto-compact for any model size). Falls back to
|
|
130
|
+
// the boot token value when the window is unknown (custom tier / pre-
|
|
131
|
+
// model-select). Display matches the dashboard's percentage-based view.
|
|
132
|
+
const effThreshold = config.tierPct != null && ctxWindow > 0
|
|
133
|
+
? Math.round(config.tierPct * ctxWindow)
|
|
134
|
+
: config.thresholdTokens;
|
|
135
|
+
const winStr = ctxWindow > 0
|
|
136
|
+
? (ctxWindow >= 1_000_000 ? `${Math.round(ctxWindow / 1_000_000)}M` : `${Math.round(ctxWindow / 1_000)}k`)
|
|
137
|
+
: "?";
|
|
138
|
+
const tierPctStr = config.tierPct != null ? `${Math.round(config.tierPct * 100)}%` : "n/a";
|
|
128
139
|
ctx.ui.notify(
|
|
129
140
|
`[mega-compact] pct=${pct} tokens=${tokens} tier=${runtime.pressureBand} (live) preset=${config.tier} ` +
|
|
130
141
|
`pressure=${Math.round(runtime.pressure * 100)}% fastGate=${config.fastGatePct}% ` +
|
|
131
|
-
`threshold=${config.
|
|
142
|
+
`threshold=${effThreshold.toLocaleString()} (${tierPctStr} of ${winStr} window) tierPct=${config.tierPct != null ? config.tierPct.toFixed(2) : "n/a"} auto=${config.auto} autoInline=${config.autoInline}\n` +
|
|
132
143
|
`[mega-compact] store: ${st.checkpointCount} chkpt · ` +
|
|
133
144
|
`${st.totalTokenEstimate} tok · last=${st.lastCheckpointId ?? "—"} · ` +
|
|
134
145
|
`injected=${st.injectedCount} · dedup=${(st.dedupHitRate * 100).toFixed(0)}%\n` +
|