pi-mega-compact 0.20.84 → 0.20.86
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/routes-rag-settings-helpers.js +7 -0
- package/dist/extensions/mega-config.js +26 -1
- package/dist/extensions/mega-events/context-handler/gateCheck.js +41 -2
- package/dist/extensions/mega-events/context-handler/thrashGuard.js +186 -0
- package/dist/extensions/mega-events/context-handler.js +33 -1
- package/dist/extensions/mega-pipeline/compact/noop.js +104 -0
- package/dist/extensions/mega-pipeline/compact/run.js +268 -0
- package/dist/extensions/mega-pipeline/compact/vote.js +72 -0
- package/dist/extensions/mega-pipeline/compact.js +12 -343
- package/dist/extensions/mega-runtime/pressure-getters.js +12 -0
- package/dist/src/failback/compact.js +109 -0
- package/dist/src/store/sqlite/meta.js +32 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +21 -0
- package/extensions/mega-config-types.ts +8 -0
- package/extensions/mega-config.ts +26 -1
- package/extensions/mega-events/context-handler/gateCheck.ts +48 -2
- package/extensions/mega-events/context-handler/thrashGuard.ts +228 -0
- package/extensions/mega-events/context-handler.ts +36 -1
- package/extensions/mega-pipeline/compact/noop.ts +96 -0
- package/extensions/mega-pipeline/compact/run.ts +322 -0
- package/extensions/mega-pipeline/compact/vote.ts +85 -0
- package/extensions/mega-pipeline/compact.ts +12 -385
- package/extensions/mega-runtime/pressure-getters.ts +14 -0
- package/package.json +1 -1
- package/src/failback/compact.ts +122 -0
- package/src/failback/types.ts +44 -0
- package/src/store/sqlite/meta.ts +36 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run.ts — full compaction pipeline (Trident) + the 3WF-2 advisory vote wiring.
|
|
3
|
+
*
|
|
4
|
+
* `runCompact` runs the full Trident pipeline (fast-gate aside) and persists a
|
|
5
|
+
* checkpoint. Moved here from extensions/mega-pipeline/compact.ts as part of
|
|
6
|
+
* the delegate-shell split (the shell re-exports the public API unchanged).
|
|
7
|
+
*
|
|
8
|
+
* Behavior change in this file vs v0.20.83: AFTER compactSession returns a
|
|
9
|
+
* non-skipped result AND the 3WF umbrella flag (config.threeWayFailback) is ON,
|
|
10
|
+
* the 3-source vote (voteCandidate) runs OBSERVATIONALLY — it logs the outcome
|
|
11
|
+
* and never mutates the result. supersede stays exactly as src/engine.ts:143
|
|
12
|
+
* (the unchanged precondition): we do NOT change compactSession, do NOT
|
|
13
|
+
* overwrite result.summary, and do NOT re-persist a checkpoint. A rejected
|
|
14
|
+
* vote (returned null) keeps the supersede-only result — which is what happens
|
|
15
|
+
* when the vote does not mutate anything. Flag OFF ⇒ the vote code does not run
|
|
16
|
+
* at all (byte-identical to v0.20.83).
|
|
17
|
+
*/
|
|
18
|
+
import { compactSession } from "../../../src/engine.js";
|
|
19
|
+
import { normalizeSessionId } from "../../../src/store.js";
|
|
20
|
+
import { repoKey } from "../../../src/store/repoKey.js";
|
|
21
|
+
import { touchSession, logDaily, incCompactCount, incCacheHitTokens } from "../../../src/store/sqlite.js";
|
|
22
|
+
import { consolidateMemories } from "../../../src/memory.js";
|
|
23
|
+
import { C, MARKER_TYPE, } from "../../mega-runtime.js";
|
|
24
|
+
import { resolveRepoRoot, preserveRecentForPressure } from "../../mega-config.js";
|
|
25
|
+
import { runRaptor } from "../../../src/dedup/raptor/index.js";
|
|
26
|
+
import { isRaptorTreeFresh } from "../../../src/dedup/raptor/buildHistory.js";
|
|
27
|
+
import { loadDedupConfig } from "../../../src/config/dedup.js";
|
|
28
|
+
import { upsertEmbedding as indexUpsertEmbedding } from "../../../src/store/vectorIndex.js";
|
|
29
|
+
import { runMemoryReview } from "../memory-review.js";
|
|
30
|
+
import { vectorList } from "../../../src/vectorStore.js";
|
|
31
|
+
import { wireCompactVote } from "./vote.js";
|
|
32
|
+
/** Run the full compaction pipeline and persist a checkpoint. Returns the result. */
|
|
33
|
+
export function runCompact(pi, runtime, config, ctx, messages, opts = {}) {
|
|
34
|
+
runtime.bindRepo(ctx.cwd);
|
|
35
|
+
const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
|
|
36
|
+
runtime.resetRuntime(sid);
|
|
37
|
+
runtime.rt.sessionId = sid;
|
|
38
|
+
const view = runtime.engineView(messages);
|
|
39
|
+
// keepFrom deepens with context pressure (Fix E): under high pressure we
|
|
40
|
+
// compact more of the session, down to the preserveRecentMin floor.
|
|
41
|
+
const preserve = preserveRecentForPressure(opts.compressionPressure ?? 0, config.preserveRecent, config.preserveRecentMin);
|
|
42
|
+
const keepFrom = opts.keepFrom ?? Math.max(0, view.length - preserve);
|
|
43
|
+
// For very small sessions (fewer messages than preserveRecent), allow
|
|
44
|
+
// compacting everything except the last message — the user explicitly
|
|
45
|
+
// requested compaction, so don't refuse it just because the session is short.
|
|
46
|
+
if (keepFrom <= 0) {
|
|
47
|
+
if (view.length <= 1)
|
|
48
|
+
return { skipped: true };
|
|
49
|
+
// Use the fallback: compact everything except the last message
|
|
50
|
+
const fallbackKeepFrom = view.length - 1;
|
|
51
|
+
return doCompact(view, fallbackKeepFrom, opts, sid, config, pi, ctx, runtime);
|
|
52
|
+
}
|
|
53
|
+
return doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime);
|
|
54
|
+
}
|
|
55
|
+
function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
|
|
56
|
+
runtime.pulsing = true; // animate the status line while the (sync) pipeline runs
|
|
57
|
+
runtime.setEffect?.("pulse", "accent", 1500); // v0.8.3: ambient border pulse during compaction
|
|
58
|
+
// S21.2: reset the per-compaction memory-op counter so the post-compact
|
|
59
|
+
// consolidate pass only fires when memory rows actually changed during the
|
|
60
|
+
// compaction window (turn_end → auto-review may have written some).
|
|
61
|
+
runtime.memoriesTouchedThisCompaction = 0;
|
|
62
|
+
const result = compactSession({
|
|
63
|
+
sessionId: sid,
|
|
64
|
+
messages: view,
|
|
65
|
+
keepFrom,
|
|
66
|
+
summary: opts.summary,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
onTier: runtime.makeTierCallback(ctx),
|
|
69
|
+
compressionPressure: opts.compressionPressure,
|
|
70
|
+
}, runtime.store);
|
|
71
|
+
runtime.pulsing = false;
|
|
72
|
+
if (result.skipped)
|
|
73
|
+
return { skipped: true };
|
|
74
|
+
if (!result.deduped) {
|
|
75
|
+
runtime.rt.persistedThisSession = true;
|
|
76
|
+
runtime.rt.lastCheckpointId = result.checkpointId;
|
|
77
|
+
}
|
|
78
|
+
runtime.rt.lastCompactedFrom = result.compactedFrom;
|
|
79
|
+
runtime.rt.lastCompactedTokens = result.tokenEstimate;
|
|
80
|
+
runtime.rt.dedupAttempts++;
|
|
81
|
+
// Honest "tokens saved" for this session-instance only:
|
|
82
|
+
// new checkpoint → original − stored
|
|
83
|
+
// deduped onto existing → whole original region (nothing new stored)
|
|
84
|
+
// Resets to 0 on session_start (rt is rebuilt) — so a fresh session shows 0
|
|
85
|
+
// while the repo's cumulative saved (SQLite meta) keeps the running total.
|
|
86
|
+
const saved = result.deduped
|
|
87
|
+
? result.originalTokenEstimate
|
|
88
|
+
: Math.max(0, result.originalTokenEstimate - result.tokenEstimate);
|
|
89
|
+
runtime.rt.tokensSaved += saved;
|
|
90
|
+
runtime.rt.compactCount += 1;
|
|
91
|
+
incCompactCount(runtime.currentStateDir);
|
|
92
|
+
if (result.deduped) {
|
|
93
|
+
runtime.rt.cacheHitTokens += saved;
|
|
94
|
+
incCacheHitTokens(saved, runtime.currentStateDir);
|
|
95
|
+
}
|
|
96
|
+
runtime.rt.lastCompactAt = Date.now();
|
|
97
|
+
if (result.deduped)
|
|
98
|
+
runtime.rt.dedupSkips++;
|
|
99
|
+
// Grow the rolling "saved" goal so the progress bar always has a fresh
|
|
100
|
+
// denominator (we don't want it pinned at 100% once we pass an old target).
|
|
101
|
+
if (runtime.rt.tokensSaved > runtime.savedGoal)
|
|
102
|
+
runtime.savedGoal = Math.ceil((runtime.rt.tokensSaved * 1.25) / 10_000) * 10_000;
|
|
103
|
+
// Live toolbar activity: what file/region just got compacted or deduped.
|
|
104
|
+
// Rendered via the rotating ticker line (see snapshot); the ring buffer is
|
|
105
|
+
// cycled one-per-repaint so the single line scrolls through recent files.
|
|
106
|
+
const files = result.filesModified ?? [];
|
|
107
|
+
const fileLabel = files.length
|
|
108
|
+
? files.map((f) => f.split("/").pop() ?? f).slice(0, 2).join(", ")
|
|
109
|
+
: result.regionHash.slice(0, 8);
|
|
110
|
+
runtime.lastActivityAt = Date.now();
|
|
111
|
+
// Explain-why line: surfaced while fresh. Pulls the dedup reason (which for
|
|
112
|
+
// L2 includes the cosine sim) so the user sees WHY a region was kept/dropped.
|
|
113
|
+
runtime.lastWhy = result.deduped
|
|
114
|
+
? `why: deduped@${result.dedupReason ?? "tier"}`
|
|
115
|
+
: `why: compacted → ${result.checkpointId}`;
|
|
116
|
+
// Recall/activity ticker: record this event in the ring buffer.
|
|
117
|
+
const savedK = (saved / 1000).toFixed(1);
|
|
118
|
+
runtime.pushTicker(result.deduped
|
|
119
|
+
? `${C.green}♻${C.reset} deduped ${fileLabel} · ${savedK}k saved`
|
|
120
|
+
: `${C.cyan}🗜${C.reset} ${result.checkpointId} · +${savedK}k · ${fileLabel}`);
|
|
121
|
+
// The per-tier trace has settled into the final outcome — fold it back into
|
|
122
|
+
// the activity line and stop showing the live trace.
|
|
123
|
+
runtime.tierTrace = undefined;
|
|
124
|
+
// Record session activity + a daily-log entry in the per-repo SQLite store
|
|
125
|
+
// (foundation for resume-sessions / daily-log features). Best-effort — never
|
|
126
|
+
// block a compaction on bookkeeping.
|
|
127
|
+
try {
|
|
128
|
+
const root = resolveRepoRoot(ctx.cwd);
|
|
129
|
+
touchSession(sid, root, runtime.currentStateDir);
|
|
130
|
+
logDaily(sid, "compact", result.checkpointId, saved, runtime.currentStateDir);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* non-fatal: stats bookkeeping only */
|
|
134
|
+
}
|
|
135
|
+
// S21.2: best-effort consolidation of near-duplicate memories for this repo.
|
|
136
|
+
// Runs after the per-repo stats touch so `consolidateMemories` can use the
|
|
137
|
+
// same stateDir. Non-fatal — a failed consolidate never blocks a compaction.
|
|
138
|
+
// Only runs when new memory ops landed in this pass (otherwise the prior
|
|
139
|
+
// compaction's consolidate already had its shot — re-running would just
|
|
140
|
+
// touch every row again with no merges).
|
|
141
|
+
if (!result.deduped && runtime.memoriesTouchedThisCompaction > 0) {
|
|
142
|
+
try {
|
|
143
|
+
const root = resolveRepoRoot(ctx.cwd);
|
|
144
|
+
void consolidateMemories(runtime.currentStateDir, root).then((n) => {
|
|
145
|
+
if (n > 0)
|
|
146
|
+
runtime.pushTicker(`${C.green}∫${C.reset} consolidated ${n} memory dup${n === 1 ? "" : "s"}`);
|
|
147
|
+
}, () => {
|
|
148
|
+
/* swallow: consolidate failures must never surface to the user */
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* non-fatal */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// S24 review-on-compact: when pressure is high, the just-compacted region is
|
|
156
|
+
// exactly the context worth remembering, so review it immediately rather than
|
|
157
|
+
// waiting for the next turn-cadence tick. Uses the shared runMemoryReview
|
|
158
|
+
// helper (fire-and-forget; doCompact is sync). Best-effort + non-fatal. Only
|
|
159
|
+
// fires above the `high` band so low-pressure compactions don't pay the cost.
|
|
160
|
+
if (!result.deduped && config.memoryAutoReview && runtime.pressureBand !== "low" && runtime.pressureBand !== "medium") {
|
|
161
|
+
void runMemoryReview(runtime, view, "pressure");
|
|
162
|
+
}
|
|
163
|
+
// Sentinel marker: a non-LLM bookkeeping entry so subsequent triggers can
|
|
164
|
+
// skip re-vectorizing an already-compacted region (zero token cost).
|
|
165
|
+
// v0.8.6: gate on !result.deduped so the marker ONLY lands when a genuinely
|
|
166
|
+
// new checkpoint was created. Without this, every dedup re-fire appended a
|
|
167
|
+
// fresh sentinel to the real transcript, bloating it and perturbing the
|
|
168
|
+
// provider KV-cache prefix (the alternating cache-miss regression). Matches
|
|
169
|
+
// the RAPTOR + vector-index blocks above, which are already !deduped-gated.
|
|
170
|
+
if (!result.deduped) {
|
|
171
|
+
pi.appendEntry(MARKER_TYPE, {
|
|
172
|
+
checkpointId: result.checkpointId,
|
|
173
|
+
regionHash: result.regionHash,
|
|
174
|
+
tokenEstimate: result.tokenEstimate,
|
|
175
|
+
deduped: result.deduped,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Fix D: refresh the RAPTOR tree for this session so live recall (search) can
|
|
179
|
+
// serve high-level summaries. Best-effort + non-fatal: never block compaction.
|
|
180
|
+
// Budget-guarded (RAPTOR_BUDGET_MS) so it can't hang a large session.
|
|
181
|
+
if (config.raptorEnabled && !result.deduped) {
|
|
182
|
+
try {
|
|
183
|
+
const dd = loadDedupConfig();
|
|
184
|
+
const all = vectorList(runtime.store, sid);
|
|
185
|
+
const leaves = all.map((cp) => ({
|
|
186
|
+
id: cp.checkpointId,
|
|
187
|
+
messages: [],
|
|
188
|
+
sourceText: cp.normalizedText ?? cp.summary ?? cp.regionHash,
|
|
189
|
+
embedding: cp.embedding,
|
|
190
|
+
}));
|
|
191
|
+
if (leaves.length >= 2) {
|
|
192
|
+
// S42D: skip the rebuild when the last build is fresh (within
|
|
193
|
+
// RAPTOR_FRESHNESS_HOURS) and the checkpoint count hasn't drifted by
|
|
194
|
+
// more than 20%. avoids re-clustering on every compaction when the
|
|
195
|
+
// tree is still representative. 0 disables (always rebuild).
|
|
196
|
+
if (dd.RAPTOR_FRESHNESS_HOURS > 0 &&
|
|
197
|
+
isRaptorTreeFresh(sid, runtime.currentStateDir, dd.RAPTOR_FRESHNESS_HOURS, all.length)) {
|
|
198
|
+
runtime.logger?.info("raptor_skip_fresh", { sessionId: sid });
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// S25: stamp the tree with the newest checkpoint epoch so the
|
|
202
|
+
// freshness guard in raptorSearchHits can reject stale trees after a
|
|
203
|
+
// later compaction adds newer checkpoints.
|
|
204
|
+
const builtAt = all.length > 0 ? Math.max(...all.map((c) => c.timestamp)) : Date.now();
|
|
205
|
+
runRaptor(leaves, {
|
|
206
|
+
stateDir: runtime.currentStateDir,
|
|
207
|
+
sessionId: sid,
|
|
208
|
+
budgetMs: dd.RAPTOR_BUDGET_MS,
|
|
209
|
+
clustersPerLevel: dd.RAPTOR_CLUSTERS_PER_LEVEL,
|
|
210
|
+
consistencyThreshold: dd.RAPTOR_CONSISTENCY,
|
|
211
|
+
logger: runtime.logger,
|
|
212
|
+
builtAt: Number.isFinite(builtAt) ? builtAt : Date.now(),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
/* non-fatal: tree refresh never blocks a compaction */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Slice 2: best-effort mirror of the new checkpoint into the async global
|
|
222
|
+
// PGlite/HNSW vector index. Fires once per compaction (not per-add), so the
|
|
223
|
+
// shared global dir is never hammered by concurrent test workers.
|
|
224
|
+
// Non-fatal: a WASM init failure degrades to the sync scan silently.
|
|
225
|
+
if (!result.deduped) {
|
|
226
|
+
try {
|
|
227
|
+
const all = vectorList(runtime.store, sid);
|
|
228
|
+
const latest = all.find((cp) => cp.checkpointId === result.checkpointId);
|
|
229
|
+
if (latest?.embedding) {
|
|
230
|
+
void indexUpsertEmbedding(repoKey(runtime.currentStateDir), sid, latest.checkpointId, latest.embedding).catch(() => {
|
|
231
|
+
/* non-fatal: index refresh never blocks a compaction */
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
/* non-fatal: index refresh never blocks a compaction */
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
runtime.setStatus(ctx, runtime.rt.persistedThisSession
|
|
240
|
+
? `mega-compact: ${result.checkpointId} · ${saved} tok saved`
|
|
241
|
+
: `mega-compact: ready`);
|
|
242
|
+
runtime.logger.info("compact", {
|
|
243
|
+
sessionId: sid,
|
|
244
|
+
checkpointId: result.checkpointId ?? "(deduped)",
|
|
245
|
+
deduped: result.deduped,
|
|
246
|
+
tokenEstimate: saved,
|
|
247
|
+
compactedFrom: result.compactedFrom,
|
|
248
|
+
});
|
|
249
|
+
runtime.dashboard.event("compact", {
|
|
250
|
+
sessionId: sid,
|
|
251
|
+
checkpointId: result.checkpointId ?? "(deduped)",
|
|
252
|
+
deduped: result.deduped,
|
|
253
|
+
tokenEstimate: saved,
|
|
254
|
+
compactedFrom: result.compactedFrom,
|
|
255
|
+
});
|
|
256
|
+
runtime.snapshot(ctx);
|
|
257
|
+
// 3WF-2: OBSERVATIONAL vote only. supersede (src/engine.ts:143) is the
|
|
258
|
+
// unchanged precondition; this never mutates result or re-persists anything.
|
|
259
|
+
// The winner label + reduction are logged for telemetry. Non-fatal: any
|
|
260
|
+
// failure here must never break the compaction above.
|
|
261
|
+
try {
|
|
262
|
+
wireCompactVote(runtime, config, sid, result, view, keepFrom);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
/* non-fatal: telemetry-only vote must never break a compaction */
|
|
266
|
+
}
|
|
267
|
+
return { skipped: false, result, keepFrom, saved };
|
|
268
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vote.ts — 3WF-2 observational vote wiring (thin adapter over
|
|
3
|
+
* src/failback/compact.ts). The ONLY behavior addition in Track A.
|
|
4
|
+
*
|
|
5
|
+
* Runs ONLY when the 3WF umbrella flag (config.threeWayFailback) is ON. After
|
|
6
|
+
* compactSession returns a non-skipped result, it votes the two competing
|
|
7
|
+
* summary candidates for the compacted region (`view.slice(0, keepFrom)` — the
|
|
8
|
+
* same slice compactSession compacts). The outcome is LOGGED (structured
|
|
9
|
+
* `compact_vote` event) and is purely observational: supersede (src/engine.ts:143)
|
|
10
|
+
* stays the unchanged precondition, result.summary is NOT overwritten, and no
|
|
11
|
+
* checkpoint is re-persisted. A null vote (rejected by the floor) means "keep
|
|
12
|
+
* the supersede-only result", which is exactly what happens when we don't touch
|
|
13
|
+
* the result. Flag OFF ⇒ this function is never called (byte-identical to
|
|
14
|
+
* v0.20.83). Non-fatal: the caller wraps it in try/catch and swallows.
|
|
15
|
+
*/
|
|
16
|
+
import { voteCandidate } from "../../../src/failback/compact.js";
|
|
17
|
+
/**
|
|
18
|
+
* Wire the observational 3-source vote after a successful compaction.
|
|
19
|
+
* @param runtime the shared mega runtime (logger + dashboard).
|
|
20
|
+
* @param config mega config (flag gate).
|
|
21
|
+
* @param sid normalized session id.
|
|
22
|
+
* @param result the compactSession result (unmodified by this call).
|
|
23
|
+
* @param view the full engine view (region = view.slice(0, keepFrom)).
|
|
24
|
+
* @param keepFrom index where the verbatim tail starts.
|
|
25
|
+
*/
|
|
26
|
+
export function wireCompactVote(runtime, config, sid, result, view, keepFrom) {
|
|
27
|
+
// Flag OFF ⇒ do nothing (byte-identical to v0.20.83 behavior).
|
|
28
|
+
if (!config.threeWayFailback)
|
|
29
|
+
return;
|
|
30
|
+
const tokensBefore = result.originalTokenEstimate;
|
|
31
|
+
const region = view.slice(0, keepFrom);
|
|
32
|
+
const winner = voteCandidate(region, tokensBefore);
|
|
33
|
+
// Observational only: pick a stable label for telemetry.
|
|
34
|
+
let label = "none";
|
|
35
|
+
let reduction = 0;
|
|
36
|
+
let signalPreserved = false;
|
|
37
|
+
let rejectedByFloor = false;
|
|
38
|
+
if (winner) {
|
|
39
|
+
// Structural label from the candidate itself — never sniff the summary text.
|
|
40
|
+
label = winner.source;
|
|
41
|
+
reduction = tokensBefore - winner.tokenEstimate;
|
|
42
|
+
signalPreserved = winner.signalPreserved;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Distinguish "no candidate" from "candidate rejected by the floor".
|
|
46
|
+
// A null return from voteCandidate means the winner scored below the floor
|
|
47
|
+
// (or there were no candidates) — i.e. keep the supersede-only result.
|
|
48
|
+
rejectedByFloor = true;
|
|
49
|
+
}
|
|
50
|
+
runtime.logger?.info("compact_vote", {
|
|
51
|
+
sessionId: sid,
|
|
52
|
+
checkpointId: result.checkpointId ?? "(deduped)",
|
|
53
|
+
winner: label,
|
|
54
|
+
reduction,
|
|
55
|
+
signalPreserved,
|
|
56
|
+
rejectedByFloor,
|
|
57
|
+
tokensBefore,
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
runtime.dashboard?.event("compact_vote", {
|
|
61
|
+
sessionId: sid,
|
|
62
|
+
checkpointId: result.checkpointId ?? "(deduped)",
|
|
63
|
+
winner: label,
|
|
64
|
+
reduction,
|
|
65
|
+
signalPreserved,
|
|
66
|
+
rejectedByFloor,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* non-fatal: dashboard probe must never break a compaction */
|
|
71
|
+
}
|
|
72
|
+
}
|