pi-mega-compact 0.4.11 → 0.4.13
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 +28 -33
- package/dist/extensions/mega-compact.js +74 -10
- package/extensions/mega-compact.ts +75 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,50 +111,45 @@ OpenAI-style contract and the `MEGACOMPACT_EMBEDDING_KEY` / `MEGACOMPACT_EMBEDDI
|
|
|
111
111
|
### Requirements
|
|
112
112
|
|
|
113
113
|
- **Node >= 18**
|
|
114
|
-
-
|
|
114
|
+
- install builds the **`better-sqlite3`** native module (one-time, local
|
|
115
115
|
compile). No network call and no API key are needed at runtime.
|
|
116
|
-
- A pi coding agent install
|
|
116
|
+
- A pi coding agent install with package support (`pi install` / `pi update
|
|
117
|
+
--extensions`). npm-installed packages are auto-discovered via the package's
|
|
118
|
+
`pi` manifest entry; local checkouts load from `~/.pi/agent/extensions/`.
|
|
117
119
|
|
|
118
120
|
### Install from npm (recommended)
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
npm
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
This places the package in `node_modules` and exposes the extension entry at
|
|
125
|
-
`node_modules/pi-mega-compact/extensions/mega-compact.ts`. Then point pi at it
|
|
126
|
-
(see "Register with pi" below).
|
|
127
|
-
|
|
128
|
-
### Register with pi
|
|
129
|
-
|
|
130
|
-
Add the extension to your pi config's `pi.extensions` list, pointing at the
|
|
131
|
-
installed entry (npm path or a symlink into pi's extensions dir — either works):
|
|
132
|
-
|
|
133
|
-
```jsonc
|
|
134
|
-
{
|
|
135
|
-
"pi": {
|
|
136
|
-
"extensions": ["pi-mega-compact/extensions/mega-compact.ts"]
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Or symlink the installed package into pi's extensions dir (the simplest path if
|
|
142
|
-
you run pi from the same machine):
|
|
122
|
+
pi installs extensions as **packages**. `pi install npm:<pkg>` writes an
|
|
123
|
+
`npm:` source into your pi config's `packages` array; pi then auto-discovers the
|
|
124
|
+
extension from the package's own `"pi": { "extensions": [...] }` manifest entry
|
|
125
|
+
— **no manual `settings.json` edit is needed**.
|
|
143
126
|
|
|
144
127
|
```bash
|
|
145
|
-
|
|
128
|
+
pi install npm:pi-mega-compact # first time: adds to packages + installs
|
|
129
|
+
pi update --extensions # thereafter: pulls the latest published version
|
|
146
130
|
```
|
|
147
131
|
|
|
148
|
-
|
|
149
|
-
|
|
132
|
+
`pi update --extensions` refreshes every `npm:` entry in `packages` (including on
|
|
133
|
+
other devices that share this config). The package ships both the TypeScript
|
|
134
|
+
source (which pi loads directly) and the compiled `dist/`, so nothing else needs
|
|
135
|
+
building.
|
|
136
|
+
|
|
137
|
+
> **Tip — keep the spec unpinned.** Use `npm:pi-mega-compact`, not
|
|
138
|
+
> `npm:pi-mega-compact@0.4.11`. Version-pinned specs are *skipped* by
|
|
139
|
+
> `pi update --extensions`, so a pin would freeze you on that release.
|
|
140
|
+
|
|
141
|
+
> **From a git checkout (development only).** To hack on the extension, clone and
|
|
142
|
+
> build locally, then symlink it into pi's extensions dir — but this bypasses the
|
|
143
|
+
> package manager, so it is NOT updated by `pi update --extensions`. Convert to the
|
|
144
|
+
> npm package (above) before shipping. The bundled `./install.sh` helper does the
|
|
145
|
+
> symlink + config edit (needs `jq`).
|
|
146
|
+
>
|
|
150
147
|
> ```bash
|
|
151
148
|
> git clone https://github.com/TheArchitectit/pi-mega-compact.git \
|
|
152
149
|
> ~/.pi/agent/extensions/pi-mega-compact
|
|
153
150
|
> cd ~/.pi/agent/extensions/pi-mega-compact
|
|
154
151
|
> npm install && npm run build
|
|
155
152
|
> ```
|
|
156
|
-
> The bundled `./install.sh` helper (`copy`) / `./install.sh -s` (`symlink`) does
|
|
157
|
-
> the same and also registers the path in pi's config (needs `jq`).
|
|
158
153
|
|
|
159
154
|
### Verify
|
|
160
155
|
|
|
@@ -166,17 +161,17 @@ npm run lint # tsc --noEmit + guardrails scan clean
|
|
|
166
161
|
### Uninstall
|
|
167
162
|
|
|
168
163
|
```bash
|
|
164
|
+
pi uninstall npm:pi-mega-compact # removes from settings.packages + the npm tree
|
|
165
|
+
# or, to keep the config entry but drop the package:
|
|
169
166
|
npm uninstall pi-mega-compact
|
|
170
167
|
```
|
|
171
168
|
|
|
172
|
-
If you symlinked it into pi's extensions dir, also remove that link:
|
|
169
|
+
If you symlinked it into pi's extensions dir (dev only), also remove that link:
|
|
173
170
|
|
|
174
171
|
```bash
|
|
175
172
|
rm -f ~/.pi/agent/extensions/pi-mega-compact
|
|
176
173
|
```
|
|
177
174
|
|
|
178
|
-
Then remove the path from pi's `pi.extensions` array.
|
|
179
|
-
|
|
180
175
|
---
|
|
181
176
|
|
|
182
177
|
## How to use it
|
|
@@ -255,25 +255,43 @@ export default function (pi) {
|
|
|
255
255
|
const usedStr = `${C.cyan}${fmt(st.totalTokenEstimate)} sess${C.reset} / ${C.blue}${fmt(repo.totalTokenEstimate)} repo${C.reset}`;
|
|
256
256
|
const agentStr = activeAgents > 0 ? ` │ 🤖 ${activeAgents} agent${activeAgents === 1 ? "" : "s"}` : "";
|
|
257
257
|
const turnStr = currentTurn > 0 ? ` │ turn ${currentTurn}` : "";
|
|
258
|
+
// Phase 3 — pulsing status glyph while a compaction is in flight.
|
|
259
|
+
const pulse = pulsing ? `${C.cyan}${PULSE[Math.floor(Date.now() / 250) % PULSE.length]}${C.reset} ` : "";
|
|
258
260
|
const lines = [
|
|
259
|
-
` ${C.amber}⚡ ${config.tier}${C.reset} │ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) │ ${st.checkpointCount} chkpt${
|
|
261
|
+
` ${C.amber}⚡ ${config.tier}${C.reset} │ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) │ ${st.checkpointCount} chkpt${agentStr}${turnStr}`,
|
|
260
262
|
` ${triggerLabel} │ ${C.magenta}dedup: ${dedupStr}${C.reset} │ ${C.gray}used:${C.reset} ${usedStr} │ ${C.gray}saved:${C.reset} ${savedStr}`,
|
|
261
263
|
];
|
|
264
|
+
// Phase 3 — compact progress bar: session tokens saved toward the rolling goal.
|
|
265
|
+
if (rt.tokensSaved > 0) {
|
|
266
|
+
const goal = Math.max(savedGoal, 1);
|
|
267
|
+
const pct = Math.min(100, Math.round((rt.tokensSaved / goal) * 100));
|
|
268
|
+
const filled = Math.round((pct / 100) * 10);
|
|
269
|
+
const bar = "▓".repeat(filled) + "░".repeat(10 - filled);
|
|
270
|
+
lines.push(` ${C.green}saved ${fmt(rt.tokensSaved)} ${bar}${C.reset} ${pct}% of ${fmt(goal)}`);
|
|
271
|
+
}
|
|
262
272
|
// Live "now processing" line — teal while fresh (≤4s), then the last-seen
|
|
263
273
|
// 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}`);
|
|
274
|
+
const fresh = Date.now() - lastActivityAt < 4000;
|
|
275
|
+
if (tierTrace && fresh) {
|
|
276
|
+
lines.push(` ${pulse}${tierTrace}`);
|
|
272
277
|
}
|
|
273
278
|
else if (currentActivity) {
|
|
274
|
-
const fresh = Date.now() - lastActivityAt < 4000;
|
|
275
279
|
lines.push(` ${fresh ? C.teal : C.dim}${currentActivity}${C.reset}`);
|
|
276
280
|
}
|
|
281
|
+
else if (pulsing) {
|
|
282
|
+
lines.push(` ${pulse}${C.teal}compacting…${C.reset}`);
|
|
283
|
+
}
|
|
284
|
+
// Phase 3 — explain-why line (fresh only).
|
|
285
|
+
if (lastWhy && fresh)
|
|
286
|
+
lines.push(` ${C.gray}${lastWhy}${C.reset}`);
|
|
287
|
+
// Phase 3 — recall/activity ticker (most-recent first), fresh only.
|
|
288
|
+
if (fresh) {
|
|
289
|
+
for (let i = ticker.length - 1; i >= 0; i--) {
|
|
290
|
+
if (lines.length >= 10)
|
|
291
|
+
break; // MAX_WIDGET_LINES guard
|
|
292
|
+
lines.push(` ${i === ticker.length - 1 ? "" : C.dim}${ticker[i].text}${C.reset}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
277
295
|
ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "aboveEditor" });
|
|
278
296
|
}
|
|
279
297
|
}
|
|
@@ -305,6 +323,24 @@ export default function (pi) {
|
|
|
305
323
|
// Built from the store's sync onTier callback during a compaction so the user
|
|
306
324
|
// watches each tier evaluate in real time. Cleared once the outcome settles.
|
|
307
325
|
let tierTrace;
|
|
326
|
+
const ticker = [];
|
|
327
|
+
const TICKER_MAX = 5;
|
|
328
|
+
function pushTicker(text) {
|
|
329
|
+
ticker.push({ text, at: Date.now() });
|
|
330
|
+
while (ticker.length > TICKER_MAX)
|
|
331
|
+
ticker.shift();
|
|
332
|
+
lastActivityAt = Date.now();
|
|
333
|
+
}
|
|
334
|
+
// Pulsing status: set true while a compaction is in flight, cleared on result.
|
|
335
|
+
let pulsing = false;
|
|
336
|
+
// Rolling "saved" goal for the progress bar — grows as we save more, so the
|
|
337
|
+
// bar always has a meaningful denominator (never sits at 100% forever).
|
|
338
|
+
let savedGoal = 50_000;
|
|
339
|
+
// Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
|
|
340
|
+
// while fresh.
|
|
341
|
+
let lastWhy = undefined;
|
|
342
|
+
// Cycling glyph phases for the pulsing status.
|
|
343
|
+
const PULSE = ["◐", "◓", "◑", "◒"];
|
|
308
344
|
// ANSI palette for the toolbar. The pi TUI's Text component preserves ANSI
|
|
309
345
|
// escape codes (see wrapTextWithAnsi), so raw escapes render as colors. No
|
|
310
346
|
// chalk dependency needed — these are just strings.
|
|
@@ -344,6 +380,10 @@ export default function (pi) {
|
|
|
344
380
|
currentActivity = undefined;
|
|
345
381
|
lastActivityAt = 0;
|
|
346
382
|
tierTrace = undefined;
|
|
383
|
+
ticker.length = 0;
|
|
384
|
+
pulsing = false;
|
|
385
|
+
savedGoal = 50_000;
|
|
386
|
+
lastWhy = undefined;
|
|
347
387
|
}
|
|
348
388
|
/** Build the sync onTier callback that paints the live per-tier trace. */
|
|
349
389
|
function makeTierCallback(ctx) {
|
|
@@ -382,6 +422,7 @@ export default function (pi) {
|
|
|
382
422
|
const keepFrom = opts.keepFrom ?? Math.max(0, view.length - config.preserveRecent);
|
|
383
423
|
if (keepFrom <= 0)
|
|
384
424
|
return { skipped: true };
|
|
425
|
+
pulsing = true; // animate the status line while the (sync) pipeline runs
|
|
385
426
|
const result = compactSession({
|
|
386
427
|
sessionId: sid,
|
|
387
428
|
messages: view,
|
|
@@ -390,6 +431,7 @@ export default function (pi) {
|
|
|
390
431
|
timestamp: Date.now(),
|
|
391
432
|
onTier: makeTierCallback(ctx),
|
|
392
433
|
}, store);
|
|
434
|
+
pulsing = false;
|
|
393
435
|
if (result.skipped)
|
|
394
436
|
return { skipped: true };
|
|
395
437
|
if (!result.deduped) {
|
|
@@ -410,6 +452,10 @@ export default function (pi) {
|
|
|
410
452
|
rt.tokensSaved += saved;
|
|
411
453
|
if (result.deduped)
|
|
412
454
|
rt.dedupSkips++;
|
|
455
|
+
// Grow the rolling "saved" goal so the progress bar always has a fresh
|
|
456
|
+
// denominator (we don't want it pinned at 100% once we pass an old target).
|
|
457
|
+
if (rt.tokensSaved > savedGoal)
|
|
458
|
+
savedGoal = Math.ceil((rt.tokensSaved * 1.25) / 10_000) * 10_000;
|
|
413
459
|
// Live toolbar "now processing" line: what file/region just got compacted or
|
|
414
460
|
// deduped. Reset to the last-seen action after a few seconds (see snapshot).
|
|
415
461
|
const files = result.filesModified ?? [];
|
|
@@ -420,6 +466,16 @@ export default function (pi) {
|
|
|
420
466
|
? `♻ deduped ${fileLabel}`
|
|
421
467
|
: `🗜 compacted ${result.checkpointId} · ${fileLabel}`;
|
|
422
468
|
lastActivityAt = Date.now();
|
|
469
|
+
// Explain-why line: surfaced while fresh. Pulls the dedup reason (which for
|
|
470
|
+
// L2 includes the cosine sim) so the user sees WHY a region was kept/dropped.
|
|
471
|
+
lastWhy = result.deduped
|
|
472
|
+
? `why: deduped@${result.dedupReason ?? "tier"}`
|
|
473
|
+
: `why: compacted → ${result.checkpointId}`;
|
|
474
|
+
// Recall/activity ticker: record this event in the ring buffer.
|
|
475
|
+
const savedK = (saved / 1000).toFixed(1);
|
|
476
|
+
pushTicker(result.deduped
|
|
477
|
+
? `${C.green}♻${C.reset} deduped ${fileLabel} · ${savedK}k saved`
|
|
478
|
+
: `${C.cyan}🗜${C.reset} ${result.checkpointId} · +${savedK}k · ${fileLabel}`);
|
|
423
479
|
// The per-tier trace has settled into the final outcome — fold it back into
|
|
424
480
|
// the activity line and stop showing the live trace.
|
|
425
481
|
tierTrace = undefined;
|
|
@@ -472,6 +528,14 @@ export default function (pi) {
|
|
|
472
528
|
const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
|
|
473
529
|
const result = recallAndInline({ sessionId: sid, query, limit: config.autoInlineK, source, skipInjected: true }, store);
|
|
474
530
|
dashboard.event("recall", { source, query: query.slice(0, 120), injected: result.toInject.length, empty: result.empty });
|
|
531
|
+
if (!result.empty && result.toInject.length > 0) {
|
|
532
|
+
const top = result.toInject[0];
|
|
533
|
+
const scorePct = Math.round((top.score ?? 0) * 100);
|
|
534
|
+
const files = top.checkpoint.filesModified ?? [];
|
|
535
|
+
const label = files.length ? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ") : top.checkpoint.checkpointId;
|
|
536
|
+
pushTicker(`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} · ${scorePct}% · ${label}`);
|
|
537
|
+
lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
|
|
538
|
+
}
|
|
475
539
|
return result;
|
|
476
540
|
}
|
|
477
541
|
// ---- Session lifecycle (state reset points) -------------------------------
|
|
@@ -358,21 +358,38 @@ export default function (pi: ExtensionAPI) {
|
|
|
358
358
|
const usedStr = `${C.cyan}${fmt(st.totalTokenEstimate)} sess${C.reset} / ${C.blue}${fmt(repo.totalTokenEstimate)} repo${C.reset}`;
|
|
359
359
|
const agentStr = activeAgents > 0 ? ` │ 🤖 ${activeAgents} agent${activeAgents === 1 ? "" : "s"}` : "";
|
|
360
360
|
const turnStr = currentTurn > 0 ? ` │ turn ${currentTurn}` : "";
|
|
361
|
+
// Phase 3 — pulsing status glyph while a compaction is in flight.
|
|
362
|
+
const pulse = pulsing ? `${C.cyan}${PULSE[Math.floor(Date.now() / 250) % PULSE.length]}${C.reset} ` : "";
|
|
361
363
|
const lines = [
|
|
362
|
-
` ${C.amber}⚡ ${config.tier}${C.reset} │ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) │ ${st.checkpointCount} chkpt${
|
|
364
|
+
` ${C.amber}⚡ ${config.tier}${C.reset} │ ${tokStr}/${maxStr} tokens (${C.bold}${pctStr}${C.reset}) │ ${st.checkpointCount} chkpt${agentStr}${turnStr}`,
|
|
363
365
|
` ${triggerLabel} │ ${C.magenta}dedup: ${dedupStr}${C.reset} │ ${C.gray}used:${C.reset} ${usedStr} │ ${C.gray}saved:${C.reset} ${savedStr}`,
|
|
364
366
|
];
|
|
367
|
+
// Phase 3 — compact progress bar: session tokens saved toward the rolling goal.
|
|
368
|
+
if (rt.tokensSaved > 0) {
|
|
369
|
+
const goal = Math.max(savedGoal, 1);
|
|
370
|
+
const pct = Math.min(100, Math.round((rt.tokensSaved / goal) * 100));
|
|
371
|
+
const filled = Math.round((pct / 100) * 10);
|
|
372
|
+
const bar = "▓".repeat(filled) + "░".repeat(10 - filled);
|
|
373
|
+
lines.push(` ${C.green}saved ${fmt(rt.tokensSaved)} ${bar}${C.reset} ${pct}% of ${fmt(goal)}`);
|
|
374
|
+
}
|
|
365
375
|
// Live "now processing" line — teal while fresh (≤4s), then the last-seen
|
|
366
376
|
// 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}`);
|
|
377
|
+
const fresh = Date.now() - lastActivityAt < 4000;
|
|
378
|
+
if (tierTrace && fresh) {
|
|
379
|
+
lines.push(` ${pulse}${tierTrace}`);
|
|
373
380
|
} else if (currentActivity) {
|
|
374
|
-
const fresh = Date.now() - lastActivityAt < 4000;
|
|
375
381
|
lines.push(` ${fresh ? C.teal : C.dim}${currentActivity}${C.reset}`);
|
|
382
|
+
} else if (pulsing) {
|
|
383
|
+
lines.push(` ${pulse}${C.teal}compacting…${C.reset}`);
|
|
384
|
+
}
|
|
385
|
+
// Phase 3 — explain-why line (fresh only).
|
|
386
|
+
if (lastWhy && fresh) lines.push(` ${C.gray}${lastWhy}${C.reset}`);
|
|
387
|
+
// Phase 3 — recall/activity ticker (most-recent first), fresh only.
|
|
388
|
+
if (fresh) {
|
|
389
|
+
for (let i = ticker.length - 1; i >= 0; i--) {
|
|
390
|
+
if (lines.length >= 10) break; // MAX_WIDGET_LINES guard
|
|
391
|
+
lines.push(` ${i === ticker.length - 1 ? "" : C.dim}${ticker[i].text}${C.reset}`);
|
|
392
|
+
}
|
|
376
393
|
}
|
|
377
394
|
ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "aboveEditor" });
|
|
378
395
|
}
|
|
@@ -406,6 +423,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
406
423
|
// Built from the store's sync onTier callback during a compaction so the user
|
|
407
424
|
// watches each tier evaluate in real time. Cleared once the outcome settles.
|
|
408
425
|
let tierTrace: string | undefined;
|
|
426
|
+
// Phase 3 — standout toolbar state.
|
|
427
|
+
// Recall/activity ticker: a small ring buffer (≤5) of recent compact/recall
|
|
428
|
+
// events so the widget shows a live history instead of a single last action.
|
|
429
|
+
interface TickerEntry { text: string; at: number; }
|
|
430
|
+
const ticker: TickerEntry[] = [];
|
|
431
|
+
const TICKER_MAX = 5;
|
|
432
|
+
function pushTicker(text: string): void {
|
|
433
|
+
ticker.push({ text, at: Date.now() });
|
|
434
|
+
while (ticker.length > TICKER_MAX) ticker.shift();
|
|
435
|
+
lastActivityAt = Date.now();
|
|
436
|
+
}
|
|
437
|
+
// Pulsing status: set true while a compaction is in flight, cleared on result.
|
|
438
|
+
let pulsing = false;
|
|
439
|
+
// Rolling "saved" goal for the progress bar — grows as we save more, so the
|
|
440
|
+
// bar always has a meaningful denominator (never sits at 100% forever).
|
|
441
|
+
let savedGoal = 50_000;
|
|
442
|
+
// Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
|
|
443
|
+
// while fresh.
|
|
444
|
+
let lastWhy: string | undefined = undefined;
|
|
445
|
+
// Cycling glyph phases for the pulsing status.
|
|
446
|
+
const PULSE = ["◐", "◓", "◑", "◒"];
|
|
409
447
|
// ANSI palette for the toolbar. The pi TUI's Text component preserves ANSI
|
|
410
448
|
// escape codes (see wrapTextWithAnsi), so raw escapes render as colors. No
|
|
411
449
|
// chalk dependency needed — these are just strings.
|
|
@@ -446,6 +484,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
446
484
|
currentActivity = undefined;
|
|
447
485
|
lastActivityAt = 0;
|
|
448
486
|
tierTrace = undefined;
|
|
487
|
+
ticker.length = 0;
|
|
488
|
+
pulsing = false;
|
|
489
|
+
savedGoal = 50_000;
|
|
490
|
+
lastWhy = undefined;
|
|
449
491
|
}
|
|
450
492
|
|
|
451
493
|
/** Build the sync onTier callback that paints the live per-tier trace. */
|
|
@@ -488,6 +530,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
488
530
|
const keepFrom = opts.keepFrom ?? Math.max(0, view.length - config.preserveRecent);
|
|
489
531
|
if (keepFrom <= 0) return { skipped: true as const };
|
|
490
532
|
|
|
533
|
+
pulsing = true; // animate the status line while the (sync) pipeline runs
|
|
491
534
|
const result = compactSession(
|
|
492
535
|
{
|
|
493
536
|
sessionId: sid,
|
|
@@ -499,6 +542,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
499
542
|
},
|
|
500
543
|
store,
|
|
501
544
|
);
|
|
545
|
+
pulsing = false;
|
|
502
546
|
|
|
503
547
|
if (result.skipped) return { skipped: true as const };
|
|
504
548
|
if (!result.deduped) {
|
|
@@ -518,6 +562,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
518
562
|
: Math.max(0, result.originalTokenEstimate - result.tokenEstimate);
|
|
519
563
|
rt.tokensSaved += saved;
|
|
520
564
|
if (result.deduped) rt.dedupSkips++;
|
|
565
|
+
// Grow the rolling "saved" goal so the progress bar always has a fresh
|
|
566
|
+
// denominator (we don't want it pinned at 100% once we pass an old target).
|
|
567
|
+
if (rt.tokensSaved > savedGoal) savedGoal = Math.ceil((rt.tokensSaved * 1.25) / 10_000) * 10_000;
|
|
521
568
|
|
|
522
569
|
// Live toolbar "now processing" line: what file/region just got compacted or
|
|
523
570
|
// deduped. Reset to the last-seen action after a few seconds (see snapshot).
|
|
@@ -529,6 +576,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
529
576
|
? `♻ deduped ${fileLabel}`
|
|
530
577
|
: `🗜 compacted ${result.checkpointId} · ${fileLabel}`;
|
|
531
578
|
lastActivityAt = Date.now();
|
|
579
|
+
// Explain-why line: surfaced while fresh. Pulls the dedup reason (which for
|
|
580
|
+
// L2 includes the cosine sim) so the user sees WHY a region was kept/dropped.
|
|
581
|
+
lastWhy = result.deduped
|
|
582
|
+
? `why: deduped@${result.dedupReason ?? "tier"}`
|
|
583
|
+
: `why: compacted → ${result.checkpointId}`;
|
|
584
|
+
// Recall/activity ticker: record this event in the ring buffer.
|
|
585
|
+
const savedK = (saved / 1000).toFixed(1);
|
|
586
|
+
pushTicker(
|
|
587
|
+
result.deduped
|
|
588
|
+
? `${C.green}♻${C.reset} deduped ${fileLabel} · ${savedK}k saved`
|
|
589
|
+
: `${C.cyan}🗜${C.reset} ${result.checkpointId} · +${savedK}k · ${fileLabel}`,
|
|
590
|
+
);
|
|
532
591
|
// The per-tier trace has settled into the final outcome — fold it back into
|
|
533
592
|
// the activity line and stop showing the live trace.
|
|
534
593
|
tierTrace = undefined;
|
|
@@ -590,6 +649,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
590
649
|
store,
|
|
591
650
|
);
|
|
592
651
|
dashboard.event("recall", { source, query: query.slice(0, 120), injected: result.toInject.length, empty: result.empty });
|
|
652
|
+
if (!result.empty && result.toInject.length > 0) {
|
|
653
|
+
const top = result.toInject[0];
|
|
654
|
+
const scorePct = Math.round((top.score ?? 0) * 100);
|
|
655
|
+
const files = top.checkpoint.filesModified ?? [];
|
|
656
|
+
const label = files.length ? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ") : top.checkpoint.checkpointId;
|
|
657
|
+
pushTicker(`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} · ${scorePct}% · ${label}`);
|
|
658
|
+
lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
|
|
659
|
+
}
|
|
593
660
|
return result;
|
|
594
661
|
}
|
|
595
662
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
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",
|