pi-mega-compact 0.8.23 → 0.8.25
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 +26 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +8 -0
- package/dist/extensions/dashboard-server/api-contracts/game-types.js +7 -0
- package/dist/extensions/mega-compact-s38.test.js +263 -14
- package/dist/extensions/mega-compact.js +15 -0
- package/dist/extensions/mega-config.js +3 -0
- package/dist/extensions/mega-events/agent-handlers.js +211 -26
- package/dist/extensions/mega-events/context-handler.js +45 -7
- package/dist/extensions/mega-events/error-classifier.js +125 -18
- package/dist/extensions/mega-pipeline/compact.js +24 -13
- package/dist/extensions/mega-pipeline/recall.js +31 -2
- package/dist/extensions/mega-runtime/append-event.js +24 -0
- package/dist/extensions/mega-runtime/bind-repo.js +65 -0
- package/dist/extensions/mega-runtime/capture-model.js +87 -0
- package/dist/extensions/mega-runtime/dashboard-snapshot.js +122 -0
- package/dist/extensions/mega-runtime/effects.js +86 -0
- package/dist/extensions/mega-runtime/engine-view.js +11 -0
- package/dist/extensions/mega-runtime/game-state.js +116 -0
- package/dist/extensions/mega-runtime/get-state-dir.js +10 -0
- package/dist/extensions/mega-runtime/perf.js +49 -0
- package/dist/extensions/mega-runtime/pressure-getters.js +64 -0
- package/dist/extensions/mega-runtime/render-widget.js +17 -0
- package/dist/extensions/mega-runtime/reset-runtime.js +50 -0
- package/dist/extensions/mega-runtime/runtime-helpers.js +73 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +208 -0
- package/dist/extensions/mega-runtime/runtime.js +405 -0
- package/dist/extensions/mega-runtime/snapshot.js +142 -0
- package/dist/extensions/mega-runtime/state.js +5 -1151
- package/dist/extensions/mega-runtime/status.js +11 -0
- package/dist/extensions/mega-runtime/widget-ansi.js +207 -0
- package/dist/extensions/mega-runtime/widget-types.js +8 -0
- package/dist/extensions/mega-runtime/widget.js +15 -204
- package/dist/extensions/openclaw-mega-compact.js +291 -0
- package/dist/src/boundary.js +79 -43
- package/dist/src/boundary.test.js +119 -2
- package/dist/src/canary.js +10 -0
- package/dist/src/config/dedup.js +14 -0
- package/dist/src/config.js +3 -1
- package/dist/src/dedup/raptor/buildHistory.js +164 -0
- package/dist/src/dedup/raptor/buildHistory.test.js +292 -0
- package/dist/src/dedup/raptor/index.js +38 -0
- package/dist/src/dedup/raptor/multilevel-serve.test.js +229 -0
- package/dist/src/dedup/raptor/multilevel.js +17 -5
- package/dist/src/dedup/raptor/multilevel.test.js +36 -1
- package/dist/src/dedup/raptor/raptor.test.js +43 -0
- package/dist/src/dedup/raptor/retrieval.js +14 -2
- package/dist/src/dedup/raptor/retrieval.test.js +95 -0
- package/dist/src/dedup/raptor/serve-gate.test.js +298 -0
- package/dist/src/dedup/raptor/summarizer.js +1 -0
- package/dist/src/dedup/raptor/tree.js +16 -2
- package/dist/src/engine.js +18 -2
- package/dist/src/httpEmbedder.js +96 -6
- package/dist/src/httpEmbedder.test.js +277 -0
- package/dist/src/mechanical-fix.test.js +65 -0
- package/dist/src/minilm.js +92 -0
- package/dist/src/raptor-inject-summaries.test.js +155 -0
- package/dist/src/recall.js +135 -21
- package/dist/src/recall.test.js +179 -4
- package/dist/src/store/sqlite/dedup-mirror.js +32 -15
- package/dist/src/store/sqlite/maintenance.js +2 -2
- package/dist/src/store/sqlite/mechanical-fix.test.js +146 -0
- package/dist/src/store/sqlite/memories.js +5 -5
- package/dist/src/store/sqlite/meta.js +1 -1
- package/dist/src/store/sqlite/raptor.js +56 -17
- package/dist/src/store/sqlite/raptor.test.js +106 -0
- package/dist/src/store/sqlite/schema.js +90 -1
- package/dist/src/store/sqlite/session-state.js +9 -3
- package/dist/src/store/sqlite/stats.js +9 -5
- package/dist/src/store/sqlite/turns.js +181 -0
- package/dist/src/store/sqlite/turns.test.js +183 -0
- package/dist/src/store/sqlite/utils.js +15 -4
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/store.js +2 -2
- package/dist/src/vector-search-cache.test.js +157 -0
- package/dist/src/vector-search.js +107 -15
- package/dist/src/vectorStore.js +36 -8
- package/dist/src/wordpiece.js +129 -0
- package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +1 -1
- package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -155
- package/extensions/dashboard-server/api-contracts/game-types.ts +172 -0
- package/extensions/mega-compact-s38.test.ts +259 -14
- package/extensions/mega-compact.ts +15 -0
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-dashboard.ts +10 -1
- package/extensions/mega-events/agent-handlers.ts +211 -26
- package/extensions/mega-events/context-handler.ts +43 -7
- package/extensions/mega-events/error-classifier.ts +125 -17
- package/extensions/mega-pipeline/compact.ts +28 -16
- package/extensions/mega-pipeline/recall.ts +34 -2
- package/extensions/mega-runtime/DECOMPOSITION.md +180 -0
- package/extensions/mega-runtime/README.md +38 -0
- package/extensions/mega-runtime/append-event.ts +40 -0
- package/extensions/mega-runtime/bind-repo.ts +81 -0
- package/extensions/mega-runtime/capture-model.ts +101 -0
- package/extensions/mega-runtime/dashboard-snapshot.ts +181 -0
- package/extensions/mega-runtime/effects.ts +129 -0
- package/extensions/mega-runtime/engine-view.ts +17 -0
- package/extensions/mega-runtime/game-state.ts +149 -0
- package/extensions/mega-runtime/get-state-dir.ts +19 -0
- package/extensions/mega-runtime/helpers.ts +25 -1
- package/extensions/mega-runtime/perf.ts +60 -0
- package/extensions/mega-runtime/pressure-getters.ts +96 -0
- package/extensions/mega-runtime/render-widget.ts +41 -0
- package/extensions/mega-runtime/runtime-helpers.ts +119 -0
- package/extensions/mega-runtime/runtime-snapshot.ts +293 -0
- package/extensions/mega-runtime/runtime.ts +483 -0
- package/extensions/mega-runtime/snapshot.ts +230 -0
- package/extensions/mega-runtime/state.ts +5 -1268
- package/extensions/mega-runtime/status.ts +26 -0
- package/extensions/mega-runtime/widget-ansi.ts +217 -0
- package/extensions/mega-runtime/widget-types.ts +80 -0
- package/extensions/mega-runtime/widget.ts +34 -285
- package/package.json +1 -1
- package/src/boundary.test.ts +128 -2
- package/src/boundary.ts +75 -39
- package/src/canary.ts +10 -0
- package/src/config/dedup.ts +25 -0
- package/src/config.ts +3 -1
- package/src/dedup/raptor/buildHistory.test.ts +353 -0
- package/src/dedup/raptor/buildHistory.ts +259 -0
- package/src/dedup/raptor/index.ts +38 -0
- package/src/dedup/raptor/multilevel-serve.test.ts +273 -0
- package/src/dedup/raptor/multilevel.test.ts +47 -0
- package/src/dedup/raptor/multilevel.ts +18 -8
- package/src/dedup/raptor/raptor.test.ts +59 -0
- package/src/dedup/raptor/retrieval.test.ts +118 -0
- package/src/dedup/raptor/retrieval.ts +14 -2
- package/src/dedup/raptor/serve-gate.test.ts +348 -0
- package/src/dedup/raptor/summarizer.ts +1 -0
- package/src/dedup/raptor/tree.ts +17 -2
- package/src/engine.ts +32 -3
- package/src/httpEmbedder.test.ts +286 -0
- package/src/httpEmbedder.ts +98 -8
- package/src/mechanical-fix.test.ts +70 -0
- package/src/raptor-inject-summaries.test.ts +212 -0
- package/src/recall.test.ts +220 -4
- package/src/recall.ts +151 -22
- package/src/store/sqlite/dedup-mirror.ts +35 -18
- package/src/store/sqlite/maintenance.ts +2 -2
- package/src/store/sqlite/mechanical-fix.test.ts +162 -0
- package/src/store/sqlite/memories.ts +5 -5
- package/src/store/sqlite/meta.ts +1 -1
- package/src/store/sqlite/raptor.test.ts +139 -0
- package/src/store/sqlite/raptor.ts +135 -81
- package/src/store/sqlite/schema.ts +90 -1
- package/src/store/sqlite/session-state.ts +9 -3
- package/src/store/sqlite/stats.ts +10 -8
- package/src/store/sqlite/turns.test.ts +218 -0
- package/src/store/sqlite/turns.ts +292 -0
- package/src/store/sqlite/utils.ts +14 -4
- package/src/store/sqlite.ts +1 -0
- package/src/store.ts +9 -2
- package/src/vector-search-cache.test.ts +190 -0
- package/src/vector-search.ts +273 -156
- package/src/vectorStore.ts +443 -382
- package/dist/extensions/dashboard-client/src/hooks/useApi.js +0 -51
- package/dist/extensions/dashboard-client/src/hooks/useSSE.js +0 -63
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime.ts — the `MegaRuntime` class: shared live state of the mega-compact
|
|
3
|
+
* extension.
|
|
4
|
+
*
|
|
5
|
+
* Phase 2d (maximal split): the class body is field declarations, the
|
|
6
|
+
* constructor, and 1-line delegates only. Every method body lives in its own
|
|
7
|
+
* module following the context-interface + free-function + thin-delegate
|
|
8
|
+
* pattern: pressure-getters.ts / append-event.ts /
|
|
9
|
+
* get-state-dir.ts / render-widget.ts / status.ts / engine-view.ts /
|
|
10
|
+
* runtime-snapshot.ts / runtime-helpers.ts / effects.ts / game-state.ts /
|
|
11
|
+
* capture-model.ts / bind-repo.ts / perf.ts. state.ts re-exports the class for
|
|
12
|
+
* backwards compatibility.
|
|
13
|
+
*/
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { VectorStore } from "../../src/vectorStore.js";
|
|
16
|
+
import { normalizeSessionId } from "../../src/store.js";
|
|
17
|
+
import { Logger } from "../../src/log.js";
|
|
18
|
+
import { Dashboard } from "../mega-dashboard.js";
|
|
19
|
+
import { ensureGameStateWatcherImpl, getCachedGameStateImpl, refreshWidgetGameStateImpl, bumpGameStateImpl, disposeRuntimeImpl, } from "./game-state.js";
|
|
20
|
+
import { setEffectImpl, armMegaCacheFlareImpl, armAchievementFlareImpl, makeTierCallbackImpl, pushTickerImpl, } from "./effects.js";
|
|
21
|
+
import { ensurePerfIntervalImpl } from "./perf.js";
|
|
22
|
+
import { captureModelImpl } from "./capture-model.js";
|
|
23
|
+
import { bindRepoImpl } from "./bind-repo.js";
|
|
24
|
+
import { snapshotImpl } from "./runtime-snapshot.js";
|
|
25
|
+
import { pressureImpl, effectiveThresholdImpl, pressureBandImpl, } from "./pressure-getters.js";
|
|
26
|
+
import { appendEventImpl } from "./append-event.js";
|
|
27
|
+
import { getStateDirImpl } from "./get-state-dir.js";
|
|
28
|
+
import { renderWidgetImpl } from "./render-widget.js";
|
|
29
|
+
import { setStatusImpl } from "./status.js";
|
|
30
|
+
import { engineViewImpl } from "./engine-view.js";
|
|
31
|
+
export class MegaRuntime {
|
|
32
|
+
config;
|
|
33
|
+
// Store/dashboard/logger are rebound per-repo by bindRepo() so each git repo
|
|
34
|
+
// gets its own isolated state dir. They start bound to the global default.
|
|
35
|
+
store;
|
|
36
|
+
logger;
|
|
37
|
+
dashboard;
|
|
38
|
+
activeRepoRoot = null;
|
|
39
|
+
currentStateDir;
|
|
40
|
+
// The only mutable per-session state. Reset on session_start / session_tree.
|
|
41
|
+
rt = {
|
|
42
|
+
sessionId: normalizeSessionId(undefined),
|
|
43
|
+
persistedThisSession: false,
|
|
44
|
+
lastCheckpointId: undefined,
|
|
45
|
+
lastCompactedFrom: 0,
|
|
46
|
+
lastCompactedTokens: 0,
|
|
47
|
+
dedupSkips: 0,
|
|
48
|
+
dedupAttempts: 0,
|
|
49
|
+
tokensSaved: 0,
|
|
50
|
+
lastCompactAt: null,
|
|
51
|
+
lastNativeCompactAt: null,
|
|
52
|
+
compactCount: 0,
|
|
53
|
+
recallInjections: 0,
|
|
54
|
+
cacheHitTokens: 0,
|
|
55
|
+
lengthStopPending: false,
|
|
56
|
+
errorRetryCount: 0,
|
|
57
|
+
errorRetryUntil: 0,
|
|
58
|
+
consecutiveErrors: 0,
|
|
59
|
+
// R1-R3 (retry redesign): in-flight dedup, session cap, poisoned-context state.
|
|
60
|
+
lastErrorRetryAt: 0,
|
|
61
|
+
retryNudgePending: false,
|
|
62
|
+
errorRetrySessionCount: 0,
|
|
63
|
+
lastErrorText: undefined,
|
|
64
|
+
errorTextRepeatCount: 0,
|
|
65
|
+
poisonedAdviseSent: false,
|
|
66
|
+
poisonedCompactSignatures: new Set(),
|
|
67
|
+
poisonedCount: 0,
|
|
68
|
+
};
|
|
69
|
+
// v0.8.6 cache-stability: the cached live-trim view for the current
|
|
70
|
+
// compaction epoch. Set after a fresh runCompact + computeLiveTrimCut, and
|
|
71
|
+
// replayed verbatim on subsequent gated context events in the SAME epoch
|
|
72
|
+
// (same checkpointId) so the provider KV-cache prefix stays stable instead
|
|
73
|
+
// of being invalidated by a freshly regenerated summary + sentinel every
|
|
74
|
+
// fire. Invalidated on session restart (resetRuntime) and on any native
|
|
75
|
+
// durable compaction (session_compact) that truncates the transcript.
|
|
76
|
+
trimCache = null;
|
|
77
|
+
debounceUntil = 0;
|
|
78
|
+
// S16: debounce for the agent_end resume nudge (avoid busy-loops).
|
|
79
|
+
resumeNudgeUntil = 0;
|
|
80
|
+
// Agent tracking for real-time widget updates
|
|
81
|
+
activeAgents = 0;
|
|
82
|
+
currentTurn = 0;
|
|
83
|
+
// S33: transient MEGA CACHE flare flag (armed by the turn_end scoring hook
|
|
84
|
+
// when cachePct > 100). Copied into widgetData.megaCacheFlare on the next
|
|
85
|
+
// snapshot() so the widget renders the oopsie gag, then reset (one cycle).
|
|
86
|
+
megaCacheFlare = false;
|
|
87
|
+
/** v0.8.3: ambient effect state for animated panel borders keyed off
|
|
88
|
+
* status transitions (level-up, mega-cache overshoot, achievement unlock,
|
|
89
|
+
* compaction start). Threaded into widgetData as `activeEffect`; the widget
|
|
90
|
+
* computes the per-frame phase from startedAt vs Date.now() (non-expired).
|
|
91
|
+
* Null when idle/expired. */
|
|
92
|
+
activeEffect = null;
|
|
93
|
+
megaCacheFlarePct = 0;
|
|
94
|
+
levelUpFlare = false;
|
|
95
|
+
lastLevel = 0;
|
|
96
|
+
// S35: transient achievement-unlock flare (armed by the scoring hooks after
|
|
97
|
+
// evaluateAndUnlockAchievements returns newly-unlocked titles). Copied into
|
|
98
|
+
// widgetData.achievementFlare on the next snapshot() so the widget renders the
|
|
99
|
+
// unlock toast, then reset (one cycle — mirrors megaCacheFlare/levelUpFlare).
|
|
100
|
+
achievementFlare = false;
|
|
101
|
+
achievementFlareTitles = [];
|
|
102
|
+
// S33: last cumulative dedup-collapsed count seen by the session_compact
|
|
103
|
+
// hook, so we only record the DELTA as the dedupe score (leaderboard sums).
|
|
104
|
+
lastDedupCollapsed = 0;
|
|
105
|
+
// Recall block produced by auto-inline (resume/branch) that the next
|
|
106
|
+
// before_agent_start should prepend to the system prompt. Unset after use.
|
|
107
|
+
pendingRecallBlock;
|
|
108
|
+
// S21: memory recall block, parallel to pendingRecallBlock. Same one-shot
|
|
109
|
+
// semantics; composed with the checkpoint block in before_agent_start.
|
|
110
|
+
pendingMemoryRecallBlock;
|
|
111
|
+
statusKey; // current status text for dashboard
|
|
112
|
+
// Active model/provider (for real cost estimation). Captured from ctx.model
|
|
113
|
+
// on model_select + session_start; persisted to SQL so cost + the dashboard
|
|
114
|
+
// can read it without a live ctx.
|
|
115
|
+
currentModel;
|
|
116
|
+
// Live "what it's doing right now" timestamp, used for the fresh-window.
|
|
117
|
+
lastActivityAt = 0;
|
|
118
|
+
// Live per-tier dedup trace (Phase 1): e.g. "L0 ✓ → L1 ✓ → L2 0.91 → stored".
|
|
119
|
+
// Built from the store's sync onTier callback during a compaction so the user
|
|
120
|
+
// watches each tier evaluate in real time. Cleared once the outcome settles.
|
|
121
|
+
tierTrace;
|
|
122
|
+
// Phase 3 — standout toolbar state.
|
|
123
|
+
// Recall/activity ticker: a small ring buffer (≤5) of recent compact/recall
|
|
124
|
+
// events so the widget shows a live history instead of a single last action.
|
|
125
|
+
ticker = [];
|
|
126
|
+
TICKER_MAX = 5;
|
|
127
|
+
// Pulsing status: set true while a compaction is in flight, cleared on result.
|
|
128
|
+
pulsing = false;
|
|
129
|
+
// S21.2: set by `applyMemoryOps` when a memory add/replace/remove lands in
|
|
130
|
+
// the current compaction. The pipeline reads this after a successful compact
|
|
131
|
+
// to decide whether to fire `consolidateMemories` (skip the work entirely
|
|
132
|
+
// when no memory rows changed).
|
|
133
|
+
memoriesTouchedThisCompaction = 0;
|
|
134
|
+
// Rolling "saved" goal for the progress bar — grows as we save more, so the
|
|
135
|
+
// bar always has a meaningful denominator (never sits at 100% forever).
|
|
136
|
+
savedGoal = 50_000;
|
|
137
|
+
// Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
|
|
138
|
+
// while fresh.
|
|
139
|
+
lastWhy = undefined;
|
|
140
|
+
// v0.8.8 Perf dashboard instrumentation: turn/provider start timestamps +
|
|
141
|
+
// the 5s cpu/mem interval handle (one per MegaRuntime, cleared in dispose()).
|
|
142
|
+
perfTurnStart = 0;
|
|
143
|
+
perfProviderStart = 0;
|
|
144
|
+
perfCpuInterval = null;
|
|
145
|
+
perfCpuBaseline;
|
|
146
|
+
// Context tracking for the dashboard (updated in the context handler).
|
|
147
|
+
lastCtxTokens = null;
|
|
148
|
+
lastCtxPercent = null;
|
|
149
|
+
lastCtxWindow = 0;
|
|
150
|
+
// Latest computed widget payload (recomputed per snapshot, rendered per frame).
|
|
151
|
+
widgetData = null;
|
|
152
|
+
// v0.8.5: material-change signature from the last full snapshot() body. When
|
|
153
|
+
// the next snapshot()'s signature matches, the expensive recompute (6 sync
|
|
154
|
+
// SQLite opens) + writeFileSync(dashboard.json) are skipped — only the
|
|
155
|
+
// (already-registered) widget factory is refreshed. Kills the per-event
|
|
156
|
+
// main-thread block during typing/idle streaming with no material change.
|
|
157
|
+
lastSnapshotSig = null;
|
|
158
|
+
// v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
|
|
159
|
+
// for in-process /mega-game writes, the fs.watch callback for cross-process
|
|
160
|
+
// dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
|
|
161
|
+
// invalidates and the widget re-reads theme/mode after the change.
|
|
162
|
+
gameStateBump = 0;
|
|
163
|
+
// Cached cross-repo drift status (recomputed at most every 30s — it opens the
|
|
164
|
+
// machine-wide registry DB, so we don't want to do it on every render frame).
|
|
165
|
+
driftCache = null;
|
|
166
|
+
// S31: cached game-mode state (game_mode_on/theme/tui_display_mode). Lazily
|
|
167
|
+
// read from the game_state SQLite row on the first widget render, then
|
|
168
|
+
// memoized until bumpGameState() evicts it (called by /mega-game after a
|
|
169
|
+
// write) so the widget picks up theme/mode/level changes live without
|
|
170
|
+
// re-querying the DB on every render frame.
|
|
171
|
+
cachedGameState;
|
|
172
|
+
// S32: fs.watch on the current repo's sqlite.db so cross-process writes
|
|
173
|
+
// (e.g. the dashboard server's PUT /api/game-state, which runs as a detached
|
|
174
|
+
// child with no MegaRuntime ref) evict the cached game-state memo. Without
|
|
175
|
+
// this, /mega-game's in-process bumpGameState() is the only eviction trigger
|
|
176
|
+
// and the widget would keep showing stale theme/mode/toggle after a dashboard
|
|
177
|
+
// edit until a restart. The watcher tracks currentStateDir — closed + re-opened
|
|
178
|
+
// by ensureGameStateWatcher() on every bindRepo repo switch. Non-fatal: any
|
|
179
|
+
// fs.watch failure (missing file / platform issue) is swallowed; the next
|
|
180
|
+
// getCachedGameState() snapshot re-queries the DB anyway.
|
|
181
|
+
gameStateWatcher;
|
|
182
|
+
gameStateWatchDir;
|
|
183
|
+
// P2: the last ExtensionContext handed to snapshot()/renderWidget(), stashed
|
|
184
|
+
// so the fs.watch game-state callback can force a widget re-render without
|
|
185
|
+
// a context event (cross-process dashboard edits while pi is idle). Cleared
|
|
186
|
+
// implicitly on construction (undefined → watcher skips until first snap).
|
|
187
|
+
lastWidgetCtx;
|
|
188
|
+
/**
|
|
189
|
+
* DIAG counters for the "team run doesn't relieve context" investigation.
|
|
190
|
+
* Plain integers, incremented at the three compaction decision points. They
|
|
191
|
+
* let a headless test drive the real event handlers and assert the firing
|
|
192
|
+
* cadence without scraping log files. Inert in production (the live-trim and
|
|
193
|
+
* before-compact probes also emit logger.info, but these counters are always
|
|
194
|
+
* updated and cost nothing).
|
|
195
|
+
*/
|
|
196
|
+
diagLiveTrimFires = 0; // context handler returned a trimmed view
|
|
197
|
+
diagLiveTrimReplays = 0; // v0.8.6: trim view returned via cached replay (skipped re-compact)
|
|
198
|
+
diagBeforeCompactFires = 0; // session_before_compact handler entered
|
|
199
|
+
diagBeforeCompactSupplied = 0; // session_before_compact supplied our trim
|
|
200
|
+
diagAgentEndIdle = 0; // agent_end with activeAgents===0
|
|
201
|
+
diagAgentEndDurable = 0; // agent_end fired ctx.compact() (mid-run durable trim)
|
|
202
|
+
diagAgentEndDurableSkipRecent = 0; // agent_end skipped ctx.compact() — compaction in last 10s (race guard)
|
|
203
|
+
// Per-skip-path counters for the team-run diagnosis.
|
|
204
|
+
diagCtxFastGate = 0; // returned at token fast-gate (below threshold)
|
|
205
|
+
diagCtxNoCompact = 0; // autoCompactCheck().shouldCompact === false
|
|
206
|
+
diagCtxDebounce = 0; // debounceUntil not yet elapsed
|
|
207
|
+
diagCtxRunSkipped = 0; // runCompact() returned skipped
|
|
208
|
+
diagCtxCutNull = 0; // computeLiveTrimCut returned null (anchor/boundary)
|
|
209
|
+
diagCtxThrown = 0; // live-trim try threw (caught)
|
|
210
|
+
/**
|
|
211
|
+
* S26 capture instrumentation: the "model_snapshots empty → $0.00 cost card"
|
|
212
|
+
* bug was invisible because captureModel swallowed the DB write in a silent
|
|
213
|
+
* `catch {}`. These always-updated counters (zero cost) let a headless test or
|
|
214
|
+
* a live capture tell whether captureModel ran and whether the snapshot landed.
|
|
215
|
+
*/
|
|
216
|
+
diagCaptureModelCalls = 0; // captureModel entered with a populated ctx.model
|
|
217
|
+
diagCaptureModelFails = 0; // recordModelSnapshot threw → model_snapshots stays empty
|
|
218
|
+
// ---- pressure accessors (bodies in pressure-getters.ts) -------------------
|
|
219
|
+
/** Live 0–1 pressure — see `pressureImpl` in pressure-getters.ts for the
|
|
220
|
+
* dual-basis (percent vs token) reconciliation notes. Thin delegate. */
|
|
221
|
+
get pressure() {
|
|
222
|
+
return pressureImpl(this);
|
|
223
|
+
}
|
|
224
|
+
/** The live compaction fire point in tokens — thin delegate to
|
|
225
|
+
* `effectiveThresholdImpl` (pressure-getters.ts). */
|
|
226
|
+
get effectiveThreshold() {
|
|
227
|
+
return effectiveThresholdImpl(this);
|
|
228
|
+
}
|
|
229
|
+
/** Live discrete pressure band (low/medium/high/ultra/mega) — thin delegate
|
|
230
|
+
* to `pressureBandImpl` (pressure-getters.ts). */
|
|
231
|
+
get pressureBand() {
|
|
232
|
+
return pressureBandImpl(this);
|
|
233
|
+
}
|
|
234
|
+
constructor(config) {
|
|
235
|
+
this.config = config;
|
|
236
|
+
this.store = new VectorStore({
|
|
237
|
+
dedupSim: config.dedupSim,
|
|
238
|
+
stateDir: config.stateDir,
|
|
239
|
+
});
|
|
240
|
+
this.logger = new Logger({
|
|
241
|
+
enabled: config.debug,
|
|
242
|
+
path: join(config.stateDir, "mega-compact.log"),
|
|
243
|
+
});
|
|
244
|
+
this.dashboard = new Dashboard(config.stateDir);
|
|
245
|
+
this.currentStateDir = config.stateDir;
|
|
246
|
+
this.ensureGameStateWatcher();
|
|
247
|
+
}
|
|
248
|
+
// ---- per-repo binding -----------------------------------------------------
|
|
249
|
+
bindRepo(cwd) {
|
|
250
|
+
return bindRepoImpl(this, cwd);
|
|
251
|
+
}
|
|
252
|
+
// ---- dashboard snapshot + widget ------------------------------------------
|
|
253
|
+
/** Collect live state and write it to disk (+ paint the above-editor widget). */
|
|
254
|
+
snapshot(ctx) {
|
|
255
|
+
snapshotImpl(this, ctx);
|
|
256
|
+
}
|
|
257
|
+
/** Width-aware above-editor widget factory registration — thin delegate to
|
|
258
|
+
* `renderWidgetImpl` (render-widget.ts). */
|
|
259
|
+
renderWidget(ctx) {
|
|
260
|
+
renderWidgetImpl(this, ctx);
|
|
261
|
+
}
|
|
262
|
+
/** Mirror the dashboard status text onto pi's status line — thin delegate to
|
|
263
|
+
* `setStatusImpl` (status.ts). */
|
|
264
|
+
setStatus(ctx, text) {
|
|
265
|
+
setStatusImpl(this, ctx, text);
|
|
266
|
+
}
|
|
267
|
+
/** Per-session state reset (session_start / session_tree) — inlined by the
|
|
268
|
+
* raptor-promotion merge (R1–R3 retry-redesign fields); reset-runtime.ts was
|
|
269
|
+
* retired by that branch. */
|
|
270
|
+
resetRuntime(sessionId) {
|
|
271
|
+
const sid = normalizeSessionId(sessionId);
|
|
272
|
+
if (this.rt.sessionId === sid && this.rt.persistedThisSession)
|
|
273
|
+
return; // same session, keep checkpoint memory
|
|
274
|
+
this.rt = {
|
|
275
|
+
sessionId: sid,
|
|
276
|
+
persistedThisSession: false,
|
|
277
|
+
lastCheckpointId: undefined,
|
|
278
|
+
lastCompactedFrom: 0,
|
|
279
|
+
lastCompactedTokens: 0,
|
|
280
|
+
dedupSkips: 0,
|
|
281
|
+
dedupAttempts: 0,
|
|
282
|
+
tokensSaved: 0,
|
|
283
|
+
lastCompactAt: null,
|
|
284
|
+
lastNativeCompactAt: null,
|
|
285
|
+
compactCount: 0,
|
|
286
|
+
recallInjections: 0,
|
|
287
|
+
cacheHitTokens: 0,
|
|
288
|
+
lengthStopPending: false,
|
|
289
|
+
errorRetryCount: 0,
|
|
290
|
+
errorRetryUntil: 0,
|
|
291
|
+
consecutiveErrors: 0,
|
|
292
|
+
// R1-R3 (retry redesign): in-flight dedup, session cap, poisoned-context state.
|
|
293
|
+
lastErrorRetryAt: 0,
|
|
294
|
+
retryNudgePending: false,
|
|
295
|
+
errorRetrySessionCount: 0,
|
|
296
|
+
lastErrorText: undefined,
|
|
297
|
+
errorTextRepeatCount: 0,
|
|
298
|
+
poisonedAdviseSent: false,
|
|
299
|
+
poisonedCompactSignatures: new Set(),
|
|
300
|
+
poisonedCount: 0,
|
|
301
|
+
};
|
|
302
|
+
this.trimCache = null; // v0.8.6: never replay a stale trim into a new session
|
|
303
|
+
this.statusKey = undefined;
|
|
304
|
+
this.activeAgents = 0;
|
|
305
|
+
this.currentTurn = 0;
|
|
306
|
+
this.lastActivityAt = 0;
|
|
307
|
+
this.tierTrace = undefined;
|
|
308
|
+
this.ticker.length = 0;
|
|
309
|
+
this.pulsing = false;
|
|
310
|
+
this.savedGoal = 50_000;
|
|
311
|
+
this.lastWhy = undefined;
|
|
312
|
+
// S31 audit P2: symmetry with bindRepo — a reset can coincide with a context
|
|
313
|
+
// that re-binds the repo, so drop the memo too. Cheap; the next
|
|
314
|
+
// getCachedGameState() re-queries lazily.
|
|
315
|
+
this.cachedGameState = undefined;
|
|
316
|
+
}
|
|
317
|
+
captureModel(ctx) {
|
|
318
|
+
captureModelImpl(this, ctx);
|
|
319
|
+
}
|
|
320
|
+
/** Structured events.log diagnostics sink (always-on) — thin delegate to
|
|
321
|
+
* `appendEventImpl` (append-event.ts). */
|
|
322
|
+
appendEvent(event, fields) {
|
|
323
|
+
appendEventImpl(this, event, fields);
|
|
324
|
+
}
|
|
325
|
+
/** S21: state dir of the currently bound repo (where memories live) — thin
|
|
326
|
+
* delegate to `getStateDirImpl` (get-state-dir.ts). */
|
|
327
|
+
getStateDir() {
|
|
328
|
+
return getStateDirImpl(this);
|
|
329
|
+
}
|
|
330
|
+
/** S32: (re)target the fs.watch cache-eviction watcher at the current
|
|
331
|
+
* stateDir's sqlite.db. Called from the constructor + every bindRepo repo
|
|
332
|
+
* switch so the watcher always tracks the NEW repo's db file. If a watcher
|
|
333
|
+
* already exists for this dir, no-op; if the dir changed, close the old one
|
|
334
|
+
* first. fs.watch can throw on a missing file / platform issues — wrapped
|
|
335
|
+
* non-fatal; the next getCachedGameState() re-queries the DB anyway. */
|
|
336
|
+
ensureGameStateWatcher() {
|
|
337
|
+
ensureGameStateWatcherImpl(this, this);
|
|
338
|
+
}
|
|
339
|
+
/** S32: release the fs.watch game-state watcher + stop the v0.8.8 perf
|
|
340
|
+
* sampling interval. Called when the runtime is torn down (no existing
|
|
341
|
+
* dispose path — the process exit reclaims the fd, but explicit close is
|
|
342
|
+
* correct for any in-process reload / test reuse). Thin delegate to
|
|
343
|
+
* `disposeRuntimeImpl` (game-state.ts). */
|
|
344
|
+
dispose() {
|
|
345
|
+
disposeRuntimeImpl(this);
|
|
346
|
+
}
|
|
347
|
+
ensurePerfInterval() {
|
|
348
|
+
ensurePerfIntervalImpl(this);
|
|
349
|
+
}
|
|
350
|
+
/** S31: the cached game-mode state (game_mode_on/theme/tui_display_mode).
|
|
351
|
+
* Lazily read from the game_state SQLite row on the first call, then
|
|
352
|
+
* memoized until `bumpGameState()` evicts it. Reading is non-throwing
|
|
353
|
+
* (getGameState returns DEFAULT_GAME_STATE on any error), so the widget
|
|
354
|
+
* can call this on every render safely. */
|
|
355
|
+
getCachedGameState() {
|
|
356
|
+
return getCachedGameStateImpl(this);
|
|
357
|
+
}
|
|
358
|
+
/** P2 cross-process re-render: lightweight game-state refresh for the
|
|
359
|
+
* fs.watch callback. Eviction of cachedGameState + gameStateBump++ happens
|
|
360
|
+
* in the caller BEFORE this runs. Here we re-read ONLY the game_state row
|
|
361
|
+
* via getCachedGameState() (one SELECT; the cache is already evicted) and
|
|
362
|
+
* patch ONLY the three game-mode fields on the EXISTING widgetData, then
|
|
363
|
+
* re-register the widget factory via renderWidget() so pi redraws next
|
|
364
|
+
* frame.
|
|
365
|
+
*
|
|
366
|
+
* WHY a lightweight path: the full snapshot(ctx) recomputes 6 synchronous
|
|
367
|
+
* SQLite opens + writeFileSync(dashboard.json) + store writes, and those
|
|
368
|
+
* store writes RETRIGGER this same fs.watch callback → re-entrant thrash
|
|
369
|
+
* (the watcher fires on every sqlite.db* write, including context-event
|
|
370
|
+
* checkpoint writes) → 190s test timeout under mega-compact.test.js /
|
|
371
|
+
* mega-teamrun.test.js. This path writes NOTHING to the store or
|
|
372
|
+
* dashboard.json, so it cannot retrigger itself.
|
|
373
|
+
*
|
|
374
|
+
* Guard: no-op when widgetData is null (no snapshot has run yet → nothing
|
|
375
|
+
* to patch) or ctx is undefined. Field values mirror snapshot() exactly. */
|
|
376
|
+
refreshWidgetGameState(ctx) {
|
|
377
|
+
refreshWidgetGameStateImpl(this, this, ctx);
|
|
378
|
+
}
|
|
379
|
+
/** S31: evict the cached game-mode state so the next widget render re-reads
|
|
380
|
+
* the game_state row. Called by /mega-game after every setGameState() so
|
|
381
|
+
* the panel picks up theme/mode/toggle changes live. */
|
|
382
|
+
bumpGameState() {
|
|
383
|
+
bumpGameStateImpl(this);
|
|
384
|
+
}
|
|
385
|
+
armMegaCacheFlare(peakPct) {
|
|
386
|
+
armMegaCacheFlareImpl(this, peakPct);
|
|
387
|
+
}
|
|
388
|
+
armAchievementFlare(titles) {
|
|
389
|
+
armAchievementFlareImpl(this, titles);
|
|
390
|
+
}
|
|
391
|
+
setEffect(type, role, durationMs) {
|
|
392
|
+
setEffectImpl(this, type, role, durationMs);
|
|
393
|
+
}
|
|
394
|
+
makeTierCallback(ctx) {
|
|
395
|
+
return makeTierCallbackImpl(this, ctx);
|
|
396
|
+
}
|
|
397
|
+
pushTicker(text) {
|
|
398
|
+
pushTickerImpl(this, text);
|
|
399
|
+
}
|
|
400
|
+
/** Convert the messages pi hands us in the `context` event into the engine
|
|
401
|
+
* view — thin delegate to `engineViewImpl` (engine-view.ts). */
|
|
402
|
+
engineView(messages) {
|
|
403
|
+
return engineViewImpl(messages);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapshot.ts — pure computation of the live stats widget data.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `MegaRuntime.snapshot()` in state.ts so the computation is
|
|
5
|
+
* a pure function with no side effects beyond returning data. The caller
|
|
6
|
+
* (state.ts) handles side-effects (level-up flare, flare consumption, render).
|
|
7
|
+
*/
|
|
8
|
+
import { C } from "./widget.js";
|
|
9
|
+
import { ownVersion } from "./helpers.js";
|
|
10
|
+
import { getTheme } from "../../src/config/themes.js";
|
|
11
|
+
// ------------------------------------------------------------------ helpers
|
|
12
|
+
function dedupStr(storageRate) {
|
|
13
|
+
// Storage dedup rate is cumulative (store-wide, per-repo) and survives
|
|
14
|
+
// session resets. Always show a number (decimal for sub-10%).
|
|
15
|
+
return storageRate * 100 >= 10
|
|
16
|
+
? `${Math.round(storageRate * 100)}%`
|
|
17
|
+
: `${(storageRate * 100).toFixed(1)}%`;
|
|
18
|
+
}
|
|
19
|
+
function pctLabel(lastCtxPercent) {
|
|
20
|
+
if (lastCtxPercent == null)
|
|
21
|
+
return "?%";
|
|
22
|
+
if (lastCtxPercent > 100)
|
|
23
|
+
return `>100%`; // S29: overshoot warning
|
|
24
|
+
return `${Math.round(lastCtxPercent * 10) / 10}%`;
|
|
25
|
+
}
|
|
26
|
+
function tokLabel(v) {
|
|
27
|
+
return v != null ? `${Math.round(v / 1000)}k` : "?";
|
|
28
|
+
}
|
|
29
|
+
function maxLabel(v) {
|
|
30
|
+
return v > 0 ? `${Math.round(v / 1000)}k` : "?";
|
|
31
|
+
}
|
|
32
|
+
function agentStr(activeAgents) {
|
|
33
|
+
const agentLabel = activeAgents > 0
|
|
34
|
+
? `\u{1F916} ${activeAgents} agent${activeAgents === 1 ? "" : "s"}`
|
|
35
|
+
: `${C.dim}\u{1F916} idle${C.reset}`;
|
|
36
|
+
return ` │ ${agentLabel}`;
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------- main computation
|
|
39
|
+
/**
|
|
40
|
+
* Pure computation of the live stats widget data.
|
|
41
|
+
*
|
|
42
|
+
* Takes a snapshot of the current MegaRuntime state and returns a fully
|
|
43
|
+
* populated `WidgetData` object plus the computed turn level. No side effects
|
|
44
|
+
* — callers are responsible for flare consumption, level-up checks, and render.
|
|
45
|
+
*/
|
|
46
|
+
export function computeMegaSnapshot(p) {
|
|
47
|
+
const st = p.st;
|
|
48
|
+
const repo = p.repo;
|
|
49
|
+
const liveBand = p.pressureBand;
|
|
50
|
+
// ── header strings ────────────────────────────────────────────────────
|
|
51
|
+
const tierLabel = `${C.bold}${liveBand}${C.reset}${C.gray}·${p.configTier}${C.reset}`;
|
|
52
|
+
const triggerLabel = p.ready
|
|
53
|
+
? `${C.green}● ready${C.reset}`
|
|
54
|
+
: p.armed
|
|
55
|
+
? `${C.amber}◐ armed${C.reset}`
|
|
56
|
+
: `${C.gray}○ idle${C.reset}`;
|
|
57
|
+
const pctStr = pctLabel(p.lastCtxPercent);
|
|
58
|
+
const tokStr = tokLabel(p.lastCtxTokens);
|
|
59
|
+
const maxStr = maxLabel(p.lastCtxWindow);
|
|
60
|
+
const dedupStr_ = dedupStr(st.storageDedupRate);
|
|
61
|
+
const agentStr_ = agentStr(p.activeAgents);
|
|
62
|
+
const turnStr = p.currentTurn > 0 ? ` │ turn ${p.currentTurn}` : "";
|
|
63
|
+
// ── reconciled in/out view (session + repo) ───────────────────────────
|
|
64
|
+
const sessIn = p.rtTokensSaved + st.totalTokenEstimate;
|
|
65
|
+
const sessKept = st.totalTokenEstimate;
|
|
66
|
+
const sessPct = sessIn > 0 ? p.rtTokensSaved / sessIn : 0;
|
|
67
|
+
const repoIn = repo.tokensSaved + repo.totalTokenEstimate;
|
|
68
|
+
const repoKept = repo.totalTokenEstimate;
|
|
69
|
+
const repoPct = repoIn > 0 ? repo.tokensSaved / repoIn : 0;
|
|
70
|
+
const sTxt = (sessPct * 100).toFixed(sessPct * 100 >= 10 ? 0 : 1);
|
|
71
|
+
const rTxt = (repoPct * 100).toFixed(repoPct * 100 >= 10 ? 0 : 1);
|
|
72
|
+
const ctxPct = p.lastCtxPercent != null ? p.lastCtxPercent / 100 : 0;
|
|
73
|
+
// ── model + provider (S26 capture) for the header ─────────────────────
|
|
74
|
+
const modelName = p.modelSnap?.modelName ?? p.modelSnap?.modelId ?? "?";
|
|
75
|
+
const modelStr = p.modelSnap?.provider
|
|
76
|
+
? `${modelName}·${p.modelSnap.provider}`
|
|
77
|
+
: modelName;
|
|
78
|
+
// ── since-last-compact (ms; null until first compaction this session) ──
|
|
79
|
+
const sinceCompact = p.lastCompactAt != null ? Date.now() - p.lastCompactAt : null;
|
|
80
|
+
// ── memory store: embedder + compression ratio ────────────────────────
|
|
81
|
+
const embedderName_ = p.embedderName();
|
|
82
|
+
const compRatio = st.originalTokens > 0 && st.totalTokenEstimate > 0
|
|
83
|
+
? st.originalTokens / st.totalTokenEstimate
|
|
84
|
+
: st.originalTokens > 0
|
|
85
|
+
? 1
|
|
86
|
+
: 0;
|
|
87
|
+
const compStr = compRatio >= 1 ? `${compRatio.toFixed(1)}x` : "—";
|
|
88
|
+
// ── cross-repo drift status ──────────────────────────────────────────
|
|
89
|
+
const driftStatus_ = p.driftStatus();
|
|
90
|
+
const agentsActive = p.activeAgents > 0;
|
|
91
|
+
// ── S31: game-mode state ──────────────────────────────────────────────
|
|
92
|
+
const gs = p.getCachedGameState();
|
|
93
|
+
const curLevel = p.getTurnLevel();
|
|
94
|
+
const cachePct = st.dedupHitRate * 100;
|
|
95
|
+
const widgetData = {
|
|
96
|
+
version: ownVersion(),
|
|
97
|
+
tierLabel,
|
|
98
|
+
triggerLabel,
|
|
99
|
+
pctStr,
|
|
100
|
+
tokStr,
|
|
101
|
+
maxStr,
|
|
102
|
+
ctxPct,
|
|
103
|
+
chk: st.checkpointCount,
|
|
104
|
+
agentStr: agentStr_,
|
|
105
|
+
turnStr,
|
|
106
|
+
dedupStr: dedupStr_,
|
|
107
|
+
sessIn,
|
|
108
|
+
sessKept,
|
|
109
|
+
sTxt,
|
|
110
|
+
repoIn,
|
|
111
|
+
repoKept,
|
|
112
|
+
rTxt,
|
|
113
|
+
repoChk: repo.checkpointCount,
|
|
114
|
+
repoSess: repo.sessionCount,
|
|
115
|
+
modelStr,
|
|
116
|
+
sinceCompact,
|
|
117
|
+
embedderName: embedderName_,
|
|
118
|
+
compStr,
|
|
119
|
+
driftStatus: driftStatus_,
|
|
120
|
+
agentsActive,
|
|
121
|
+
fresh: Date.now() - p.lastActivityAt < 4000,
|
|
122
|
+
ticker: p.ticker,
|
|
123
|
+
lastWhy: p.lastWhy,
|
|
124
|
+
tierTrace: p.tierTrace,
|
|
125
|
+
pulsing: p.pulsing,
|
|
126
|
+
// S31 game-mode fields:
|
|
127
|
+
gameMode: gs.game_mode_on,
|
|
128
|
+
theme: getTheme(gs.theme) ? gs.theme : "transparent",
|
|
129
|
+
tuiMode: gs.tui_display_mode,
|
|
130
|
+
level: curLevel,
|
|
131
|
+
cachePct,
|
|
132
|
+
megaCacheFlare: p.megaCacheFlare,
|
|
133
|
+
megaCacheFlarePct: p.megaCacheFlarePct,
|
|
134
|
+
levelUpFlare: p.levelUpFlare,
|
|
135
|
+
achievementFlare: p.achievementFlare,
|
|
136
|
+
achievementFlareTitles: p.achievementFlareTitles,
|
|
137
|
+
// v0.8.3: ambient border effect — threaded live so the widget can
|
|
138
|
+
// compute the per-frame phase and render animated borders.
|
|
139
|
+
activeEffect: p.activeEffect,
|
|
140
|
+
};
|
|
141
|
+
return { widgetData, curLevel };
|
|
142
|
+
}
|