pi-mega-compact 0.8.23 → 0.8.24

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.
Files changed (55) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +8 -0
  2. package/dist/extensions/dashboard-server/api-contracts/game-types.js +7 -0
  3. package/dist/extensions/mega-runtime/append-event.js +24 -0
  4. package/dist/extensions/mega-runtime/bind-repo.js +65 -0
  5. package/dist/extensions/mega-runtime/capture-model.js +87 -0
  6. package/dist/extensions/mega-runtime/dashboard-snapshot.js +118 -0
  7. package/dist/extensions/mega-runtime/effects.js +86 -0
  8. package/dist/extensions/mega-runtime/engine-view.js +11 -0
  9. package/dist/extensions/mega-runtime/game-state.js +116 -0
  10. package/dist/extensions/mega-runtime/get-state-dir.js +10 -0
  11. package/dist/extensions/mega-runtime/perf.js +49 -0
  12. package/dist/extensions/mega-runtime/pressure-getters.js +64 -0
  13. package/dist/extensions/mega-runtime/render-widget.js +17 -0
  14. package/dist/extensions/mega-runtime/reset-runtime.js +50 -0
  15. package/dist/extensions/mega-runtime/runtime-helpers.js +73 -0
  16. package/dist/extensions/mega-runtime/runtime-snapshot.js +204 -0
  17. package/dist/extensions/mega-runtime/runtime.js +352 -0
  18. package/dist/extensions/mega-runtime/snapshot.js +142 -0
  19. package/dist/extensions/mega-runtime/state.js +5 -1151
  20. package/dist/extensions/mega-runtime/status.js +11 -0
  21. package/dist/extensions/mega-runtime/widget-ansi.js +207 -0
  22. package/dist/extensions/mega-runtime/widget-types.js +8 -0
  23. package/dist/extensions/mega-runtime/widget.js +15 -204
  24. package/dist/extensions/openclaw-mega-compact.js +291 -0
  25. package/dist/src/minilm.js +92 -0
  26. package/dist/src/wordpiece.js +129 -0
  27. package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +1 -1
  28. package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -155
  29. package/extensions/dashboard-server/api-contracts/game-types.ts +172 -0
  30. package/extensions/mega-runtime/DECOMPOSITION.md +180 -0
  31. package/extensions/mega-runtime/README.md +38 -0
  32. package/extensions/mega-runtime/append-event.ts +40 -0
  33. package/extensions/mega-runtime/bind-repo.ts +81 -0
  34. package/extensions/mega-runtime/capture-model.ts +101 -0
  35. package/extensions/mega-runtime/dashboard-snapshot.ts +173 -0
  36. package/extensions/mega-runtime/effects.ts +129 -0
  37. package/extensions/mega-runtime/engine-view.ts +17 -0
  38. package/extensions/mega-runtime/game-state.ts +149 -0
  39. package/extensions/mega-runtime/get-state-dir.ts +19 -0
  40. package/extensions/mega-runtime/perf.ts +60 -0
  41. package/extensions/mega-runtime/pressure-getters.ts +96 -0
  42. package/extensions/mega-runtime/render-widget.ts +41 -0
  43. package/extensions/mega-runtime/reset-runtime.ts +80 -0
  44. package/extensions/mega-runtime/runtime-helpers.ts +119 -0
  45. package/extensions/mega-runtime/runtime-snapshot.ts +289 -0
  46. package/extensions/mega-runtime/runtime.ts +437 -0
  47. package/extensions/mega-runtime/snapshot.ts +230 -0
  48. package/extensions/mega-runtime/state.ts +5 -1268
  49. package/extensions/mega-runtime/status.ts +26 -0
  50. package/extensions/mega-runtime/widget-ansi.ts +217 -0
  51. package/extensions/mega-runtime/widget-types.ts +80 -0
  52. package/extensions/mega-runtime/widget.ts +34 -285
  53. package/package.json +1 -1
  54. package/dist/extensions/dashboard-client/src/hooks/useApi.js +0 -51
  55. package/dist/extensions/dashboard-client/src/hooks/useSSE.js +0 -63
@@ -0,0 +1,17 @@
1
+ /**
2
+ * render-widget.ts — extracted `MegaRuntime.renderWidget()`: the width-aware
3
+ * above-editor widget factory registration. Same thin-delegate pattern as the
4
+ * other runtime.ts extractions.
5
+ */
6
+ import { WIDGET_KEY } from "./helpers.js";
7
+ import { buildWidgetLines } from "./widget.js";
8
+ // -------------------------------------------------------------- renderWidget
9
+ /** Register the above-editor widget as a width-aware factory so pi re-renders
10
+ * it at the REAL terminal width every frame (auto-fit wide/narrow). The
11
+ * factory returns a minimal Component whose render() reads self.widgetData. */
12
+ export function renderWidgetImpl(self, ctx) {
13
+ ctx.ui.setWidget(WIDGET_KEY, (_tui, _theme) => ({
14
+ render: (width) => buildWidgetLines(self.widgetData, width > 0 ? width : 200, self.activeAgents),
15
+ invalidate: () => { },
16
+ }), { placement: "aboveEditor" });
17
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * reset-runtime.ts — extracted `MegaRuntime.resetRuntime()`: the per-session
3
+ * state reset used by the session_start / session_tree handlers. The class
4
+ * keeps a thin `resetRuntimeImpl(this, sessionId)` delegate so every call
5
+ * site is unchanged.
6
+ *
7
+ * Follows the same context-interface + free-function + thin-delegate pattern as
8
+ * effects.ts / game-state.ts / capture-model.ts / bind-repo.ts / perf.ts /
9
+ * runtime-helpers.ts.
10
+ */
11
+ import { normalizeSessionId } from "../../src/store.js";
12
+ // --------------------------------------------------------------- resetRuntime
13
+ export function resetRuntimeImpl(self, sessionId) {
14
+ const sid = normalizeSessionId(sessionId);
15
+ if (self.rt.sessionId === sid && self.rt.persistedThisSession)
16
+ return; // same session, keep checkpoint memory
17
+ self.rt = {
18
+ sessionId: sid,
19
+ persistedThisSession: false,
20
+ lastCheckpointId: undefined,
21
+ lastCompactedFrom: 0,
22
+ lastCompactedTokens: 0,
23
+ dedupSkips: 0,
24
+ dedupAttempts: 0,
25
+ tokensSaved: 0,
26
+ lastCompactAt: null,
27
+ lastNativeCompactAt: null,
28
+ compactCount: 0,
29
+ recallInjections: 0,
30
+ cacheHitTokens: 0,
31
+ lengthStopPending: false,
32
+ errorRetryCount: 0,
33
+ errorRetryUntil: 0,
34
+ consecutiveErrors: 0,
35
+ };
36
+ self.trimCache = null; // v0.8.6: never replay a stale trim into a new session
37
+ self.statusKey = undefined;
38
+ self.activeAgents = 0;
39
+ self.currentTurn = 0;
40
+ self.lastActivityAt = 0;
41
+ self.tierTrace = undefined;
42
+ self.ticker.length = 0;
43
+ self.pulsing = false;
44
+ self.savedGoal = 50_000;
45
+ self.lastWhy = undefined;
46
+ // S31 audit P2: symmetry with bindRepo — a reset can coincide with a context
47
+ // that re-binds the repo, so drop the memo too. Cheap; the next
48
+ // getCachedGameState() re-queries lazily.
49
+ self.cachedGameState = undefined;
50
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * runtime-helpers.ts — extracted private helpers from the `MegaRuntime` class
3
+ * (runtime.ts) so the class body shrinks and the pure/instance logic is
4
+ * independently testable.
5
+ *
6
+ * Follows the same context-interface + free-function + thin-delegate pattern as
7
+ * effects.ts / game-state.ts / capture-model.ts / bind-repo.ts / perf.ts.
8
+ */
9
+ import { detectCrossRepoDrift } from "../../src/driftDetection.js";
10
+ import { turnLevel } from "../../src/game/scoring.js";
11
+ // -------------------------------------------------------------- materialSig
12
+ /** v0.8.5: cheap material-change signature over live runtime fields (no
13
+ * SQLite). Two snapshots with the same signature produce identical
14
+ * dashboard.json + widgetData, so the 6 synchronous SQLite opens + the
15
+ * writeFileSync(dashboard.json) can be skipped. Built from in-memory state
16
+ * only; `gameStateBump` covers cross-process game_state edits (fs.watch) +
17
+ * in-process /mega-game writes (bumpGameState) + repo switches (bindRepo).
18
+ * The transient flare flags are included so a one-shot flare forces the
19
+ * recompute that renders (then clears) it for exactly one cycle. */
20
+ export function materialSigImpl(ctx) {
21
+ const rt = ctx.rt;
22
+ const ae = ctx.activeEffect;
23
+ return JSON.stringify([
24
+ ctx.lastCtxTokens, ctx.lastCtxPercent, ctx.lastCtxWindow,
25
+ ctx.activeAgents, ctx.currentTurn,
26
+ rt.compactCount, rt.tokensSaved, rt.dedupSkips, rt.dedupAttempts,
27
+ rt.recallInjections, rt.cacheHitTokens, rt.persistedThisSession,
28
+ rt.lastCheckpointId ?? null, rt.lastCompactedFrom, rt.lastCompactedTokens,
29
+ ctx.statusKey ?? null,
30
+ ctx.currentModel?.modelId ?? null, ctx.currentModel?.provider ?? null,
31
+ ae ? `${ae.type}:${ae.role}:${ae.startedAt}` : null,
32
+ ctx.gameStateBump,
33
+ ctx.megaCacheFlare, ctx.megaCacheFlarePct,
34
+ ctx.levelUpFlare, ctx.achievementFlare,
35
+ ctx.achievementFlareTitles.join("|"),
36
+ ctx.tierTrace ?? null, ctx.lastWhy ?? null, ctx.pulsing,
37
+ ctx.ticker.length,
38
+ ]);
39
+ }
40
+ // -------------------------------------------------------------- embedderName
41
+ /** Active embedder name for the memory-store line (Trigram default / MiniLM).
42
+ * Pure — reads only `process.env` (the same flag the embedder factory reads). */
43
+ export function embedderNameImpl() {
44
+ // MINILM_EMBEDDER flag lives in src/config/dedup.ts; read the same env var
45
+ // the embedder factory uses so the label matches what's actually running.
46
+ return process.env.MEGACOMPACT_MINILM === "true" ||
47
+ process.env.MEGACOMPACT_MINILM === "1"
48
+ ? "MiniLM"
49
+ : "Trigram";
50
+ }
51
+ // -------------------------------------------------------------- driftStatus
52
+ /** Cross-repo drift status (ok | warn), cached for 30s (opens the registry DB). */
53
+ export function driftStatusImpl(ctx) {
54
+ const now = Date.now();
55
+ if (ctx.driftCache && now - ctx.driftCache.at < 30_000)
56
+ return ctx.driftCache.status;
57
+ let status = "ok";
58
+ try {
59
+ const report = detectCrossRepoDrift();
60
+ status = report.totals.warn > 0 ? "warn" : "ok";
61
+ }
62
+ catch {
63
+ status = "ok";
64
+ }
65
+ ctx.driftCache = { at: now, status };
66
+ return status;
67
+ }
68
+ // -------------------------------------------------------------- getTurnLevel
69
+ /** S33: player level for game mode — floor(log2(turns+1))+1 (gentle).
70
+ * Defensive: non-finite/negative collapses to 1 (never NaN). */
71
+ export function getTurnLevelImpl(ctx) {
72
+ return turnLevel(ctx.currentTurn);
73
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * runtime-snapshot.ts — extracted `MegaRuntime.snapshot()` orchestration.
3
+ *
4
+ * The snapshot() body (dashboard write + S39 heartbeat + widget-data compute
5
+ * + flare/effect consumption + perf recording + the v0.8.5 material-change
6
+ * gate) is moved here verbatim. `MegaRuntime.snapshot()` becomes a thin
7
+ * delegate (`snapshotImpl(this, ctx)`), so the public method and every call
8
+ * site is unchanged.
9
+ *
10
+ * Follows the same context-interface + free-function + thin-delegate pattern
11
+ * as effects.ts / game-state.ts / capture-model.ts / runtime-helpers.ts.
12
+ * The pure widget-data computation lives in snapshot.ts (`computeMegaSnapshot`);
13
+ * this module is the orchestration that calls it. The pure/instance helpers
14
+ * materialSig/embedderName/driftStatus/getTurnLevel are called directly via
15
+ * their `*Impl` functions (imported from runtime-helpers.ts) — they were only
16
+ * ever called from snapshot(), so the in-class private delegates are removed
17
+ * from runtime.ts (dead code) rather than kept.
18
+ */
19
+ import { join } from "node:path";
20
+ import { vectorStats, vectorRepoStats, vectorDataInvariant, } from "../../src/vectorStore.js";
21
+ import { latestModelSnapshot, recordPerfSample, recordSessionHeartbeat, appendTokenSample, } from "../../src/store/sqlite.js";
22
+ import { resolveRepoRoot } from "../mega-config.js";
23
+ import { computeMegaSnapshot } from "./snapshot.js";
24
+ import { buildDashboardSnapshot } from "./dashboard-snapshot.js";
25
+ import { materialSigImpl, embedderNameImpl, driftStatusImpl, getTurnLevelImpl, } from "./runtime-helpers.js";
26
+ // -------------------------------------------------------------- snapshotImpl
27
+ /** Collect live state and write it to disk (+ paint the above-editor widget).
28
+ * Extracted verbatim from `MegaRuntime.snapshot()` (runtime.ts); the public
29
+ * method there is now `snapshotImpl(this, ctx)`. */
30
+ export function snapshotImpl(self, ctx) {
31
+ if (ctx)
32
+ self.lastWidgetCtx = ctx;
33
+ if (ctx)
34
+ self.bindRepo(ctx.cwd);
35
+ // v0.8.5: gate the expensive body (6 sync SQLite opens +
36
+ // writeFileSync(dashboard.json)) behind a cheap material-change signature.
37
+ // During typing / idle / no-compaction streaming, the 'context' event
38
+ // fires repeatedly with NO material change — skip the recompute + write and
39
+ // just re-register the (live) widget factory, which reads the cached
40
+ // widgetData every frame. This removes the per-event main-thread block
41
+ // WITHOUT changing write timing, so tests that read dashboard.json
42
+ // synchronously after a compaction still see it written (compaction changes
43
+ // compactCount/tokensSaved → the signature changes → the full recompute +
44
+ // write runs).
45
+ const sig = materialSigImpl(self);
46
+ if (ctx && self.widgetData && self.lastSnapshotSig === sig) {
47
+ self.renderWidget(ctx);
48
+ return;
49
+ }
50
+ const perfT0 = performance.now();
51
+ const st = vectorStats(self.store, self.rt.sessionId);
52
+ const repo = vectorRepoStats(self.store);
53
+ const di = vectorDataInvariant(self.store);
54
+ // Effective threshold + armed/ready status for the dashboard.
55
+ // effectiveThresholdPct: the live fire point as a % of the window (null for
56
+ // `custom`, which has no tierPct). S29: honors MEGACOMPACT_AUTO_PCT_TRIGGER
57
+ // override so the dashboard's armed/ready match the context-handler gate
58
+ // (which fires on this same %). Used by armed/ready + the dashboard.
59
+ const effectiveThresholdPct = self.config.tierPct != null
60
+ ? (self.config.autoPctTrigger ?? self.config.tierPct) * 100
61
+ : null;
62
+ // armed lights at/above the REAL fire point: max(effectiveThresholdPct,
63
+ // fastGatePct). fastGatePct already equals tierPct*100 by default, but a
64
+ // MEGACOMPACT_FAST_GATE_PCT override can raise it, so we take the max.
65
+ const armed = self.lastCtxPercent != null &&
66
+ self.lastCtxPercent >=
67
+ Math.max(effectiveThresholdPct ?? 0, self.config.fastGatePct);
68
+ // S29: ready mirrors the context-handler gate's basis — percent for tiered
69
+ // (the gate fires on pct), tokens for custom (the gate fires on tokens).
70
+ // Previously this always required tokens, so the dashboard could show
71
+ // "armed" (percent high) but never "ready" when tokens were under-reported
72
+ // — the same inconsistency the S29 gate fix removes.
73
+ const ready = self.config.tierPct != null
74
+ ? armed && (self.lastCtxPercent ?? 0) >= (effectiveThresholdPct ?? 0)
75
+ : armed && (self.lastCtxTokens ?? 0) >= self.effectiveThreshold;
76
+ self.dashboard.snapshot(buildDashboardSnapshot({
77
+ config: self.config,
78
+ rt: self.rt,
79
+ pressureBand: self.pressureBand,
80
+ pressure: self.pressure,
81
+ effectiveThreshold: self.effectiveThreshold,
82
+ statusKey: self.statusKey,
83
+ lastCtxTokens: self.lastCtxTokens,
84
+ lastCtxPercent: self.lastCtxPercent,
85
+ lastCtxWindow: self.lastCtxWindow,
86
+ diagCtxFastGate: self.diagCtxFastGate,
87
+ diagLiveTrimFires: self.diagLiveTrimFires,
88
+ diagLiveTrimReplays: self.diagLiveTrimReplays,
89
+ errorRetryCount: self.rt.errorRetryCount,
90
+ consecutiveErrors: self.rt.consecutiveErrors,
91
+ ERROR_RETRY_MAX_CONSECUTIVE: self.config.maxConsecutiveErrors,
92
+ errorRetryHardStop: self.config.errorRetryHardStop,
93
+ activeAgents: self.activeAgents,
94
+ currentTurn: self.currentTurn,
95
+ currentModel: self.currentModel,
96
+ st,
97
+ repo,
98
+ di,
99
+ }));
100
+ const perfDiskMs = self.dashboard.lastWriteMs;
101
+ // S39: record a session heartbeat + token sample into the shared
102
+ // machine-wide index.sqlite so the dashboard can show a real-time
103
+ // stacked-memory graph across all active pi processes. Behind the
104
+ // material-change gate (this code only runs when sig changed). Non-fatal
105
+ // try/catch mirrors the recordPerfSample pattern below. Skip the token
106
+ // sample when lastCtxTokens is null (no context data yet).
107
+ try {
108
+ const repo = resolveRepoRoot(ctx?.cwd ?? self.currentStateDir) ?? self.currentStateDir;
109
+ recordSessionHeartbeat(process.pid, self.rt.sessionId, repo, self.currentStateDir, self.lastCtxWindow || 0);
110
+ if (self.lastCtxTokens != null) {
111
+ appendTokenSample(self.rt.sessionId, repo, self.lastCtxTokens, self.lastCtxPercent ?? 0, self.lastCtxWindow || 0, join(self.currentStateDir, "events.log"));
112
+ }
113
+ }
114
+ catch {
115
+ /* non-fatal: S39 monitoring must never block the snapshot path */
116
+ }
117
+ // Live stats widget above the editor
118
+ if (ctx) {
119
+ // S31: game-mode state — fetched before the widget computation so the
120
+ // pure function gets a plain value rather than another callback.
121
+ const gs = self.getCachedGameState();
122
+ // S34: derive the level-up flare from the turn count. This side-effect
123
+ // check must happen BEFORE computeMegaSnapshot so the flare and the
124
+ // ambient effect are armed for the current frame.
125
+ const curLevel = getTurnLevelImpl(self);
126
+ if (curLevel > self.lastLevel) {
127
+ self.levelUpFlare = true;
128
+ // v0.8.3: arm a pulse border effect to celebrate the level-up.
129
+ self.setEffect("pulse", "accent", 1500);
130
+ }
131
+ // ── gather widget data (computed per snapshot, rendered per frame) ────
132
+ const modelSnap = latestModelSnapshot(self.currentStateDir);
133
+ const _snapResult = computeMegaSnapshot({
134
+ lastCtxTokens: self.lastCtxTokens,
135
+ lastCtxWindow: self.lastCtxWindow,
136
+ lastCtxPercent: self.lastCtxPercent,
137
+ activeAgents: self.activeAgents,
138
+ currentTurn: self.currentTurn,
139
+ statusKey: self.statusKey,
140
+ st,
141
+ repo,
142
+ rtTokensSaved: self.rt.tokensSaved,
143
+ lastCompactAt: self.rt.lastCompactAt,
144
+ ticker: self.ticker,
145
+ lastWhy: self.lastWhy,
146
+ tierTrace: self.tierTrace,
147
+ pulsing: self.pulsing,
148
+ getCachedGameState: () => gs,
149
+ getTurnLevel: () => getTurnLevelImpl(self),
150
+ embedderName: () => embedderNameImpl(),
151
+ driftStatus: () => driftStatusImpl(self),
152
+ megaCacheFlare: self.megaCacheFlare,
153
+ megaCacheFlarePct: self.megaCacheFlarePct,
154
+ levelUpFlare: self.levelUpFlare,
155
+ achievementFlare: self.achievementFlare,
156
+ achievementFlareTitles: self.achievementFlareTitles,
157
+ activeEffect: self.activeEffect,
158
+ lastActivityAt: self.lastActivityAt,
159
+ pressureBand: self.pressureBand,
160
+ configTier: self.config.tier,
161
+ ready,
162
+ armed,
163
+ modelSnap,
164
+ });
165
+ self.widgetData = _snapResult.widgetData;
166
+ // S33: consume the flare after copying it into widgetData so it fires
167
+ // for exactly one render cycle (the gag flares once, then clears).
168
+ self.megaCacheFlare = false;
169
+ self.megaCacheFlarePct = 0;
170
+ // S34: consume the level-up flare after one render cycle (mirrors the
171
+ // megaCacheFlare one-shot semantics), and advance lastLevel.
172
+ self.levelUpFlare = false;
173
+ self.lastLevel = curLevel;
174
+ // S35: consume the achievement-unlock flare after one render cycle
175
+ // (mirrors the megaCacheFlare/levelUpFlare one-shot semantics).
176
+ self.achievementFlare = false;
177
+ self.achievementFlareTitles = [];
178
+ // v0.8.3: expire the ambient border effect once its time window has
179
+ // elapsed. SEPARATE from the one-shot flares above (those are per-cycle
180
+ // consumes; activeEffect is time-windowed and cleared when Date.now()
181
+ // crosses startedAt + durationMs). The widget also defends this per-frame
182
+ // (effectBorderSgr returns '' once expired), so this is bookkeeping to
183
+ // free the slot and prevent a stale effect lingering between snapshots.
184
+ if (self.activeEffect &&
185
+ Date.now() - self.activeEffect.startedAt >=
186
+ self.activeEffect.durationMs) {
187
+ self.activeEffect = null;
188
+ }
189
+ // Auto-fit: register a factory so pi re-renders the panel at the REAL
190
+ // terminal width every frame (tui.columns), instead of guessing with
191
+ // process.stdout.columns. buildWidgetLines reads this.widgetData live.
192
+ self.renderWidget(ctx);
193
+ }
194
+ // v0.8.5: record the material-change signature computed at the top so the
195
+ // next snapshot() can skip this whole body when nothing material changed.
196
+ try {
197
+ recordPerfSample(self.currentStateDir, "db_recompute_ms", performance.now() - perfT0);
198
+ recordPerfSample(self.currentStateDir, "disk_write_ms", perfDiskMs);
199
+ }
200
+ catch {
201
+ /* non-fatal: perf instrumentation never blocks the agent */
202
+ }
203
+ self.lastSnapshotSig = sig;
204
+ }