pi-mega-compact 0.8.3 โ 0.8.5
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/dashboard-server/html.js +1 -1
- package/dist/extensions/dashboard-server/html.test.js +11 -0
- package/dist/extensions/mega-compact.js +9 -0
- package/dist/extensions/mega-runtime/state.js +60 -0
- package/extensions/dashboard-server/html.test.ts +12 -0
- package/extensions/dashboard-server/html.ts +1 -1
- package/extensions/mega-compact.ts +9 -0
- package/extensions/mega-runtime/state.ts +61 -0
- package/package.json +1 -1
|
@@ -932,7 +932,7 @@ export function dashboardHtml(tierName) {
|
|
|
932
932
|
if (best != null && best > 100) {
|
|
933
933
|
tile.style.display = 'block';
|
|
934
934
|
tile.className = 'achievement-tile unlocked';
|
|
935
|
-
tile.innerHTML = '๐ Opie
|
|
935
|
+
tile.innerHTML = '๐ Opie\\'s Wild Ride<span class="ach-detail">best ' + sanitize(fmtPct(best)) + ' ยท first ' + sanitize(fmtDate(firstSeen)) + '</span>';
|
|
936
936
|
} else { tile.style.display = 'none'; tile.className = 'achievement-tile'; }
|
|
937
937
|
}
|
|
938
938
|
// transient oopsie toast: a NEW mega_cache trophy row since the last poll
|
|
@@ -39,3 +39,14 @@ describe("S35 dashboard HTML achievements", () => {
|
|
|
39
39
|
assert.ok(html.includes("??? "), "??? teaser present");
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
|
+
describe("S35 dashboard HTML achievements escaping (v0.8.4 fix)", () => {
|
|
43
|
+
const html = dashboardHtml("custom");
|
|
44
|
+
test("Opie's Wild Ride JS string is backslash-escaped (no bare apostrophe)", () => {
|
|
45
|
+
// The served <script> uses single-quoted JS strings: '๐ Opie\'s Wild Ride...'.
|
|
46
|
+
// A bare apostrophe (Opie's) would terminate the string and halt the script,
|
|
47
|
+
// breaking .tab click bindings. The served HTML MUST contain the escaped
|
|
48
|
+
// form Opie\'s and MUST NOT contain the bare form '๐ Opie's'.
|
|
49
|
+
assert.ok(html.includes("๐ Opie\\'s Wild Ride"), "served JS contains Opie\\'s (escaped)");
|
|
50
|
+
assert.ok(!html.includes("๐ Opie's Wild Ride"), "no bare Opie's in served JS");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -42,4 +42,13 @@ export default function (pi) {
|
|
|
42
42
|
registerConflictCommands(pi, runtime);
|
|
43
43
|
registerDbCommands(pi, runtime);
|
|
44
44
|
registerGameCommands(pi, runtime);
|
|
45
|
+
// v0.8.5 (audit P3): release the fs.watch game-state watcher handle on
|
|
46
|
+
// session teardown so it doesn't linger across reloads. pi exposes no
|
|
47
|
+
// extension-unload event (the factory return value is ignored and there is no
|
|
48
|
+
// "shutdown" event on the ExtensionAPI), so dispose() is wired to the
|
|
49
|
+
// session_shutdown lifecycle event โ the closest valid teardown signal.
|
|
50
|
+
// dispose() is idempotent, and the next snapshot() re-opens the watcher
|
|
51
|
+
// lazily via bindRepo() โ ensureGameStateWatcher(), so there is no permanent
|
|
52
|
+
// leak and no per-session fd accumulation.
|
|
53
|
+
pi.on("session_shutdown", () => runtime.dispose());
|
|
45
54
|
}
|
|
@@ -119,6 +119,17 @@ export class MegaRuntime {
|
|
|
119
119
|
lastCtxWindow = 0;
|
|
120
120
|
// Latest computed widget payload (recomputed per snapshot, rendered per frame).
|
|
121
121
|
widgetData = null;
|
|
122
|
+
// v0.8.5: material-change signature from the last full snapshot() body. When
|
|
123
|
+
// the next snapshot()'s signature matches, the expensive recompute (6 sync
|
|
124
|
+
// SQLite opens) + writeFileSync(dashboard.json) are skipped โ only the
|
|
125
|
+
// (already-registered) widget factory is refreshed. Kills the per-event
|
|
126
|
+
// main-thread block during typing/idle streaming with no material change.
|
|
127
|
+
lastSnapshotSig = null;
|
|
128
|
+
// v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
|
|
129
|
+
// for in-process /mega-game writes, the fs.watch callback for cross-process
|
|
130
|
+
// dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
|
|
131
|
+
// invalidates and the widget re-reads theme/mode after the change.
|
|
132
|
+
gameStateBump = 0;
|
|
122
133
|
// Cached cross-repo drift status (recomputed at most every 30s โ it opens the
|
|
123
134
|
// machine-wide registry DB, so we don't want to do it on every render frame).
|
|
124
135
|
driftCache = null;
|
|
@@ -253,6 +264,7 @@ export class MegaRuntime {
|
|
|
253
264
|
// stateDir), so evict the memo on every repo switch; the next widget render
|
|
254
265
|
// re-queries lazily via getCachedGameState().
|
|
255
266
|
this.cachedGameState = undefined;
|
|
267
|
+
this.gameStateBump++;
|
|
256
268
|
// S32: re-target the fs.watch cache-eviction watcher at the NEW stateDir's
|
|
257
269
|
// sqlite.db so cross-process writes (dashboard server) still evict the memo.
|
|
258
270
|
this.ensureGameStateWatcher();
|
|
@@ -293,6 +305,21 @@ export class MegaRuntime {
|
|
|
293
305
|
snapshot(ctx) {
|
|
294
306
|
if (ctx)
|
|
295
307
|
this.bindRepo(ctx.cwd);
|
|
308
|
+
// v0.8.5: gate the expensive body (6 sync SQLite opens +
|
|
309
|
+
// writeFileSync(dashboard.json)) behind a cheap material-change signature.
|
|
310
|
+
// During typing / idle / no-compaction streaming, the 'context' event
|
|
311
|
+
// fires repeatedly with NO material change โ skip the recompute + write and
|
|
312
|
+
// just re-register the (live) widget factory, which reads the cached
|
|
313
|
+
// widgetData every frame. This removes the per-event main-thread block
|
|
314
|
+
// WITHOUT changing write timing, so tests that read dashboard.json
|
|
315
|
+
// synchronously after a compaction still see it written (compaction changes
|
|
316
|
+
// compactCount/tokensSaved โ the signature changes โ the full recompute +
|
|
317
|
+
// write runs).
|
|
318
|
+
const sig = this.materialSig();
|
|
319
|
+
if (ctx && this.widgetData && this.lastSnapshotSig === sig) {
|
|
320
|
+
this.renderWidget(ctx);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
296
323
|
const st = this.store.stats(this.rt.sessionId);
|
|
297
324
|
const repo = this.store.repoStats();
|
|
298
325
|
const di = this.store.dataInvariant();
|
|
@@ -596,6 +623,9 @@ export class MegaRuntime {
|
|
|
596
623
|
// process.stdout.columns. buildWidgetLines reads this.widgetData live.
|
|
597
624
|
this.renderWidget(ctx);
|
|
598
625
|
}
|
|
626
|
+
// v0.8.5: record the material-change signature computed at the top so the
|
|
627
|
+
// next snapshot() can skip this whole body when nothing material changed.
|
|
628
|
+
this.lastSnapshotSig = sig;
|
|
599
629
|
}
|
|
600
630
|
/** Register the above-editor widget as a width-aware factory so pi re-renders
|
|
601
631
|
* it at the REAL terminal width every frame (auto-fit wide/narrow). The
|
|
@@ -607,6 +637,34 @@ export class MegaRuntime {
|
|
|
607
637
|
invalidate: () => { },
|
|
608
638
|
}), { placement: "aboveEditor" });
|
|
609
639
|
}
|
|
640
|
+
/** v0.8.5: cheap material-change signature over live runtime fields (no
|
|
641
|
+
* SQLite). Two snapshots with the same signature produce identical
|
|
642
|
+
* dashboard.json + widgetData, so the 6 synchronous SQLite opens + the
|
|
643
|
+
* writeFileSync(dashboard.json) can be skipped. Built from in-memory state
|
|
644
|
+
* only; gameStateBump covers cross-process game_state edits (fs.watch) +
|
|
645
|
+
* in-process /mega-game writes (bumpGameState) + repo switches (bindRepo).
|
|
646
|
+
* The transient flare flags are included so a one-shot flare forces the
|
|
647
|
+
* recompute that renders (then clears) it for exactly one cycle. */
|
|
648
|
+
materialSig() {
|
|
649
|
+
const rt = this.rt;
|
|
650
|
+
const ae = this.activeEffect;
|
|
651
|
+
return JSON.stringify([
|
|
652
|
+
this.lastCtxTokens, this.lastCtxPercent, this.lastCtxWindow,
|
|
653
|
+
this.activeAgents, this.currentTurn,
|
|
654
|
+
rt.compactCount, rt.tokensSaved, rt.dedupSkips, rt.dedupAttempts,
|
|
655
|
+
rt.recallInjections, rt.cacheHitTokens, rt.persistedThisSession,
|
|
656
|
+
rt.lastCheckpointId ?? null, rt.lastCompactedFrom, rt.lastCompactedTokens,
|
|
657
|
+
this.statusKey ?? null,
|
|
658
|
+
this.currentModel?.modelId ?? null, this.currentModel?.provider ?? null,
|
|
659
|
+
ae ? `${ae.type}:${ae.role}:${ae.startedAt}` : null,
|
|
660
|
+
this.gameStateBump,
|
|
661
|
+
this.megaCacheFlare, this.megaCacheFlarePct,
|
|
662
|
+
this.levelUpFlare, this.achievementFlare,
|
|
663
|
+
this.achievementFlareTitles.join("|"),
|
|
664
|
+
this.tierTrace ?? null, this.lastWhy ?? null, this.pulsing,
|
|
665
|
+
this.ticker.length,
|
|
666
|
+
]);
|
|
667
|
+
}
|
|
610
668
|
/** Active embedder name for the memory-store line (Trigram default / MiniLM). */
|
|
611
669
|
embedderName() {
|
|
612
670
|
// MINILM_EMBEDDER flag lives in src/config/dedup.ts; read the same env var
|
|
@@ -803,6 +861,7 @@ export class MegaRuntime {
|
|
|
803
861
|
this.gameStateWatcher = watch(this.currentStateDir, (_eventType, filename) => {
|
|
804
862
|
if (typeof filename === "string" && filename.startsWith("sqlite.db")) {
|
|
805
863
|
this.cachedGameState = undefined;
|
|
864
|
+
this.gameStateBump++;
|
|
806
865
|
}
|
|
807
866
|
});
|
|
808
867
|
this.gameStateWatchDir = this.currentStateDir;
|
|
@@ -849,6 +908,7 @@ export class MegaRuntime {
|
|
|
849
908
|
* the panel picks up theme/mode/toggle changes live. */
|
|
850
909
|
bumpGameState() {
|
|
851
910
|
this.cachedGameState = undefined;
|
|
911
|
+
this.gameStateBump++;
|
|
852
912
|
}
|
|
853
913
|
/** S33: player level for game mode โ floor(log2(turns+1))+1 (gentle).
|
|
854
914
|
* Defensive: non-finite/negative collapses to 1 (never NaN). */
|
|
@@ -48,3 +48,15 @@ describe("S35 dashboard HTML achievements", () => {
|
|
|
48
48
|
assert.ok(html.includes("??? "), "??? teaser present");
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
describe("S35 dashboard HTML achievements escaping (v0.8.4 fix)", () => {
|
|
53
|
+
const html = dashboardHtml("custom");
|
|
54
|
+
test("Opie's Wild Ride JS string is backslash-escaped (no bare apostrophe)", () => {
|
|
55
|
+
// The served <script> uses single-quoted JS strings: '๐ Opie\'s Wild Ride...'.
|
|
56
|
+
// A bare apostrophe (Opie's) would terminate the string and halt the script,
|
|
57
|
+
// breaking .tab click bindings. The served HTML MUST contain the escaped
|
|
58
|
+
// form Opie\'s and MUST NOT contain the bare form '๐ Opie's'.
|
|
59
|
+
assert.ok(html.includes("๐ Opie\\'s Wild Ride"), "served JS contains Opie\\'s (escaped)");
|
|
60
|
+
assert.ok(!html.includes("๐ Opie's Wild Ride"), "no bare Opie's in served JS");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -935,7 +935,7 @@ export function dashboardHtml(tierName: string): string {
|
|
|
935
935
|
if (best != null && best > 100) {
|
|
936
936
|
tile.style.display = 'block';
|
|
937
937
|
tile.className = 'achievement-tile unlocked';
|
|
938
|
-
tile.innerHTML = '๐ Opie
|
|
938
|
+
tile.innerHTML = '๐ Opie\\'s Wild Ride<span class="ach-detail">best ' + sanitize(fmtPct(best)) + ' ยท first ' + sanitize(fmtDate(firstSeen)) + '</span>';
|
|
939
939
|
} else { tile.style.display = 'none'; tile.className = 'achievement-tile'; }
|
|
940
940
|
}
|
|
941
941
|
// transient oopsie toast: a NEW mega_cache trophy row since the last poll
|
|
@@ -45,4 +45,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
45
45
|
registerConflictCommands(pi, runtime);
|
|
46
46
|
registerDbCommands(pi, runtime);
|
|
47
47
|
registerGameCommands(pi, runtime);
|
|
48
|
+
// v0.8.5 (audit P3): release the fs.watch game-state watcher handle on
|
|
49
|
+
// session teardown so it doesn't linger across reloads. pi exposes no
|
|
50
|
+
// extension-unload event (the factory return value is ignored and there is no
|
|
51
|
+
// "shutdown" event on the ExtensionAPI), so dispose() is wired to the
|
|
52
|
+
// session_shutdown lifecycle event โ the closest valid teardown signal.
|
|
53
|
+
// dispose() is idempotent, and the next snapshot() re-opens the watcher
|
|
54
|
+
// lazily via bindRepo() โ ensureGameStateWatcher(), so there is no permanent
|
|
55
|
+
// leak and no per-session fd accumulation.
|
|
56
|
+
pi.on("session_shutdown", () => runtime.dispose());
|
|
48
57
|
}
|
|
@@ -158,6 +158,17 @@ export class MegaRuntime {
|
|
|
158
158
|
|
|
159
159
|
// Latest computed widget payload (recomputed per snapshot, rendered per frame).
|
|
160
160
|
widgetData: WidgetData | null = null;
|
|
161
|
+
// v0.8.5: material-change signature from the last full snapshot() body. When
|
|
162
|
+
// the next snapshot()'s signature matches, the expensive recompute (6 sync
|
|
163
|
+
// SQLite opens) + writeFileSync(dashboard.json) are skipped โ only the
|
|
164
|
+
// (already-registered) widget factory is refreshed. Kills the per-event
|
|
165
|
+
// main-thread block during typing/idle streaming with no material change.
|
|
166
|
+
private lastSnapshotSig: string | null = null;
|
|
167
|
+
// v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
|
|
168
|
+
// for in-process /mega-game writes, the fs.watch callback for cross-process
|
|
169
|
+
// dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
|
|
170
|
+
// invalidates and the widget re-reads theme/mode after the change.
|
|
171
|
+
private gameStateBump = 0;
|
|
161
172
|
// Cached cross-repo drift status (recomputed at most every 30s โ it opens the
|
|
162
173
|
// machine-wide registry DB, so we don't want to do it on every render frame).
|
|
163
174
|
private driftCache: { at: number; status: "ok" | "warn" } | null = null;
|
|
@@ -303,6 +314,7 @@ export class MegaRuntime {
|
|
|
303
314
|
// stateDir), so evict the memo on every repo switch; the next widget render
|
|
304
315
|
// re-queries lazily via getCachedGameState().
|
|
305
316
|
this.cachedGameState = undefined;
|
|
317
|
+
this.gameStateBump++;
|
|
306
318
|
// S32: re-target the fs.watch cache-eviction watcher at the NEW stateDir's
|
|
307
319
|
// sqlite.db so cross-process writes (dashboard server) still evict the memo.
|
|
308
320
|
this.ensureGameStateWatcher();
|
|
@@ -343,6 +355,21 @@ export class MegaRuntime {
|
|
|
343
355
|
/** Collect live state and write it to disk (+ paint the above-editor widget). */
|
|
344
356
|
snapshot(ctx?: ExtensionContext): void {
|
|
345
357
|
if (ctx) this.bindRepo(ctx.cwd);
|
|
358
|
+
// v0.8.5: gate the expensive body (6 sync SQLite opens +
|
|
359
|
+
// writeFileSync(dashboard.json)) behind a cheap material-change signature.
|
|
360
|
+
// During typing / idle / no-compaction streaming, the 'context' event
|
|
361
|
+
// fires repeatedly with NO material change โ skip the recompute + write and
|
|
362
|
+
// just re-register the (live) widget factory, which reads the cached
|
|
363
|
+
// widgetData every frame. This removes the per-event main-thread block
|
|
364
|
+
// WITHOUT changing write timing, so tests that read dashboard.json
|
|
365
|
+
// synchronously after a compaction still see it written (compaction changes
|
|
366
|
+
// compactCount/tokensSaved โ the signature changes โ the full recompute +
|
|
367
|
+
// write runs).
|
|
368
|
+
const sig = this.materialSig();
|
|
369
|
+
if (ctx && this.widgetData && this.lastSnapshotSig === sig) {
|
|
370
|
+
this.renderWidget(ctx);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
346
373
|
const st = this.store.stats(this.rt.sessionId);
|
|
347
374
|
const repo = this.store.repoStats();
|
|
348
375
|
const di = this.store.dataInvariant();
|
|
@@ -664,6 +691,9 @@ export class MegaRuntime {
|
|
|
664
691
|
// process.stdout.columns. buildWidgetLines reads this.widgetData live.
|
|
665
692
|
this.renderWidget(ctx);
|
|
666
693
|
}
|
|
694
|
+
// v0.8.5: record the material-change signature computed at the top so the
|
|
695
|
+
// next snapshot() can skip this whole body when nothing material changed.
|
|
696
|
+
this.lastSnapshotSig = sig;
|
|
667
697
|
}
|
|
668
698
|
|
|
669
699
|
/** Register the above-editor widget as a width-aware factory so pi re-renders
|
|
@@ -686,6 +716,35 @@ export class MegaRuntime {
|
|
|
686
716
|
);
|
|
687
717
|
}
|
|
688
718
|
|
|
719
|
+
/** v0.8.5: cheap material-change signature over live runtime fields (no
|
|
720
|
+
* SQLite). Two snapshots with the same signature produce identical
|
|
721
|
+
* dashboard.json + widgetData, so the 6 synchronous SQLite opens + the
|
|
722
|
+
* writeFileSync(dashboard.json) can be skipped. Built from in-memory state
|
|
723
|
+
* only; gameStateBump covers cross-process game_state edits (fs.watch) +
|
|
724
|
+
* in-process /mega-game writes (bumpGameState) + repo switches (bindRepo).
|
|
725
|
+
* The transient flare flags are included so a one-shot flare forces the
|
|
726
|
+
* recompute that renders (then clears) it for exactly one cycle. */
|
|
727
|
+
private materialSig(): string {
|
|
728
|
+
const rt = this.rt;
|
|
729
|
+
const ae = this.activeEffect;
|
|
730
|
+
return JSON.stringify([
|
|
731
|
+
this.lastCtxTokens, this.lastCtxPercent, this.lastCtxWindow,
|
|
732
|
+
this.activeAgents, this.currentTurn,
|
|
733
|
+
rt.compactCount, rt.tokensSaved, rt.dedupSkips, rt.dedupAttempts,
|
|
734
|
+
rt.recallInjections, rt.cacheHitTokens, rt.persistedThisSession,
|
|
735
|
+
rt.lastCheckpointId ?? null, rt.lastCompactedFrom, rt.lastCompactedTokens,
|
|
736
|
+
this.statusKey ?? null,
|
|
737
|
+
this.currentModel?.modelId ?? null, this.currentModel?.provider ?? null,
|
|
738
|
+
ae ? `${ae.type}:${ae.role}:${ae.startedAt}` : null,
|
|
739
|
+
this.gameStateBump,
|
|
740
|
+
this.megaCacheFlare, this.megaCacheFlarePct,
|
|
741
|
+
this.levelUpFlare, this.achievementFlare,
|
|
742
|
+
this.achievementFlareTitles.join("|"),
|
|
743
|
+
this.tierTrace ?? null, this.lastWhy ?? null, this.pulsing,
|
|
744
|
+
this.ticker.length,
|
|
745
|
+
]);
|
|
746
|
+
}
|
|
747
|
+
|
|
689
748
|
/** Active embedder name for the memory-store line (Trigram default / MiniLM). */
|
|
690
749
|
private embedderName(): string {
|
|
691
750
|
// MINILM_EMBEDDER flag lives in src/config/dedup.ts; read the same env var
|
|
@@ -887,6 +946,7 @@ export class MegaRuntime {
|
|
|
887
946
|
(_eventType, filename) => {
|
|
888
947
|
if (typeof filename === "string" && filename.startsWith("sqlite.db")) {
|
|
889
948
|
this.cachedGameState = undefined;
|
|
949
|
+
this.gameStateBump++;
|
|
890
950
|
}
|
|
891
951
|
},
|
|
892
952
|
);
|
|
@@ -932,6 +992,7 @@ export class MegaRuntime {
|
|
|
932
992
|
* the panel picks up theme/mode/toggle changes live. */
|
|
933
993
|
bumpGameState(): void {
|
|
934
994
|
this.cachedGameState = undefined;
|
|
995
|
+
this.gameStateBump++;
|
|
935
996
|
}
|
|
936
997
|
|
|
937
998
|
/** S33: player level for game mode โ floor(log2(turns+1))+1 (gentle).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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",
|