pi-mega-compact 0.11.2 → 0.11.4
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/api-contracts/endpoints.js +9 -1
- package/dist/extensions/dashboard-server/api-contracts/provider-cache.js +7 -0
- package/dist/extensions/dashboard-server/api-contracts.test.js +3 -2
- package/dist/extensions/dashboard-server/index-reader.js +22 -0
- package/dist/extensions/dashboard-server/routes-cache.js +80 -0
- package/dist/extensions/dashboard-server/routes-cache.test.js +248 -0
- package/dist/extensions/dashboard-server/routes-maintenance.js +7 -7
- package/dist/extensions/dashboard-server/routes-repo.js +24 -0
- package/dist/extensions/dashboard-server/routes.js +1 -0
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/extensions/mega-cache-replay.test.js +233 -24
- package/dist/extensions/mega-compact-s38.test.js +778 -20
- package/dist/extensions/mega-config.js +4 -0
- package/dist/extensions/mega-events/agent-handlers.js +44 -8
- package/dist/extensions/mega-events/context-handler.js +37 -19
- package/dist/extensions/mega-events/error-classifier.js +125 -135
- package/dist/extensions/mega-events/outage-advisor.js +53 -0
- package/dist/extensions/mega-runtime/reset-runtime.js +1 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +14 -4
- package/dist/extensions/mega-runtime/runtime.js +1 -0
- package/dist/extensions/mega-runtime/snapshot.js +1 -1
- package/dist/extensions/mega-runtime/snapshot.test.js +127 -0
- package/dist/extensions/mega-runtime/widget.js +4 -4
- package/dist/extensions/mega-runtime/widget.test.js +103 -12
- package/dist/src/pricing.js +78 -0
- package/dist/src/store/sqlite/perf-samples.js +104 -0
- package/dist/src/store/sqlite/perf-samples.test.js +93 -1
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cml6YC8w.js → AchievementTiles-vEo2MtE2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cml6YC8w.js.map → AchievementTiles-vEo2MtE2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/AchievementsTab-DjxfYrDc.js +2 -0
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-B8NqDt7I.js.map → AchievementsTab-DjxfYrDc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CBYvp2mV.js → ConfigTab-Cm6ymbmx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CBYvp2mV.js.map → ConfigTab-Cm6ymbmx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DlTJqiDm.js → EventsTab-DXWl_YzI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DlTJqiDm.js.map → EventsTab-DXWl_YzI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-DkGTqKJW.js → GameTab-BLDudcO5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-DkGTqKJW.js.map → GameTab-BLDudcO5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-DLkKCztn.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-DLkKCztn.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-BFo7pKM-.js → MetricsTab-Cq4u-rZF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-BFo7pKM-.js.map → MetricsTab-Cq4u-rZF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CClCUd3-.js → OverviewTab-D4kGvsiQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CClCUd3-.js.map → OverviewTab-D4kGvsiQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js +2 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CCYd-kSz.js → SessionsTab-DZC2-GCn.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CCYd-kSz.js.map → SessionsTab-DZC2-GCn.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js +2 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DRLJmVv0.js → TopicsTab-01G71hv1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DRLJmVv0.js.map → TopicsTab-01G71hv1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CLNM0T5R.js → TurnsTab-DLRg0LZT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CLNM0T5R.js.map → TurnsTab-DLRg0LZT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/index-BEdKlpAB.css +1 -0
- package/extensions/dashboard-client/dist/assets/{index-BcoSz-L4.js → index-BX0bcgLu.js} +4 -4
- package/extensions/dashboard-client/dist/assets/{index-BcoSz-L4.js.map → index-BX0bcgLu.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-Cezlih5V.js → useSSE-BBYt5UPB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-Cezlih5V.js.map → useSSE-BBYt5UPB.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/api/client.ts +6 -0
- package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +13 -1
- package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +141 -0
- package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +31 -3
- package/extensions/dashboard-client/src/main.tsx +1 -0
- package/extensions/dashboard-client/src/styles/maintenance.css +267 -0
- package/extensions/dashboard-client/src/styles/overview-extra.css +9 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +83 -29
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +301 -136
- package/extensions/dashboard-client/src/utils/format.ts +42 -7
- package/extensions/dashboard-server/api-contracts/endpoints.ts +31 -8
- package/extensions/dashboard-server/api-contracts/index.ts +4 -0
- package/extensions/dashboard-server/api-contracts/multi-repo.ts +6 -0
- package/extensions/dashboard-server/api-contracts/provider-cache.ts +30 -0
- package/extensions/dashboard-server/api-contracts.test.ts +3 -2
- package/extensions/dashboard-server/index-reader.ts +23 -0
- package/extensions/dashboard-server/routes-cache.test.ts +302 -0
- package/extensions/dashboard-server/routes-cache.ts +123 -0
- package/extensions/dashboard-server/routes-maintenance.ts +18 -11
- package/extensions/dashboard-server/routes-repo.ts +31 -4
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/extensions/mega-cache-replay.test.ts +481 -166
- package/extensions/mega-compact-s38.test.ts +785 -20
- package/extensions/mega-config.ts +14 -0
- package/extensions/mega-events/agent-handlers.ts +48 -7
- package/extensions/mega-events/context-handler.ts +59 -24
- package/extensions/mega-events/error-classifier.ts +167 -129
- package/extensions/mega-events/outage-advisor.ts +73 -0
- package/extensions/mega-runtime/helpers.ts +2 -0
- package/extensions/mega-runtime/reset-runtime.ts +1 -0
- package/extensions/mega-runtime/runtime-snapshot.ts +31 -9
- package/extensions/mega-runtime/runtime.ts +1 -0
- package/extensions/mega-runtime/snapshot.test.ts +139 -0
- package/extensions/mega-runtime/snapshot.ts +6 -4
- package/extensions/mega-runtime/widget.test.ts +290 -105
- package/extensions/mega-runtime/widget.ts +29 -6
- package/package.json +1 -1
- package/src/pricing.ts +95 -0
- package/src/store/sqlite/perf-samples.test.ts +107 -3
- package/src/store/sqlite/perf-samples.ts +142 -6
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B8NqDt7I.js +0 -2
- package/extensions/dashboard-client/dist/assets/CacheTab-BCLzVL4S.js +0 -2
- package/extensions/dashboard-client/dist/assets/CacheTab-BCLzVL4S.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Cw43zmUx.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Cw43zmUx.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/ReposTab-_3R4RSLZ.js +0 -2
- package/extensions/dashboard-client/dist/assets/ReposTab-_3R4RSLZ.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-0TqsrY6y.js +0 -2
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-0TqsrY6y.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-gn66lYD2.css +0 -1
|
@@ -63,6 +63,9 @@ export interface MegaConfig {
|
|
|
63
63
|
/** High-pressure floor for preserveRecent — when context is near the limit
|
|
64
64
|
* we compact deeper, but never below this (keeps recent turns for coherence). */
|
|
65
65
|
preserveRecentMin: number;
|
|
66
|
+
/** D.1: minimum context-growth percentage delta before re-compacting instead of
|
|
67
|
+
* replaying the cached trim (Default 50). Set via MEGACOMPACT_RECOMPACT_PCT_DELTA.*/
|
|
68
|
+
recompactPctDelta: number;
|
|
66
69
|
auto: boolean;
|
|
67
70
|
autoInline: boolean;
|
|
68
71
|
autoInlineK: number;
|
|
@@ -104,6 +107,10 @@ export interface MegaConfig {
|
|
|
104
107
|
* classification is upgraded to 'poisoned-context' (the stateful repeat
|
|
105
108
|
* signal). Default 3. Raise to make the upgrade less aggressive. */
|
|
106
109
|
poisonedContextRepeatThreshold: number;
|
|
110
|
+
/** R10: consecutive transient errors at which a calm "provider outage"
|
|
111
|
+
* advisory is sent to the user (distinct from the poisoned /clear advise).
|
|
112
|
+
* Default 3. `0` disables the advisory entirely. */
|
|
113
|
+
providerOutageAdviseThreshold: number;
|
|
107
114
|
/** S29: override the auto-compact fire point for tiered configs, as a
|
|
108
115
|
* fraction of the context window (e.g. 0.85). null = inherit the tier's
|
|
109
116
|
* tierPct (default; preserves existing fire points). The context-handler
|
|
@@ -295,6 +302,10 @@ export function loadConfig(): MegaConfig {
|
|
|
295
302
|
"MEGACOMPACT_POISONED_REPEAT_THRESHOLD",
|
|
296
303
|
3,
|
|
297
304
|
),
|
|
305
|
+
providerOutageAdviseThreshold: envFlag(
|
|
306
|
+
"MEGACOMPACT_PROVIDER_OUTAGE_THRESHOLD",
|
|
307
|
+
3,
|
|
308
|
+
),
|
|
298
309
|
autoPctTrigger,
|
|
299
310
|
autoInlineK: envFlag("MEGACOMPACT_AUTO_INLINE_K", 3),
|
|
300
311
|
dedupSim: Number(process.env.MEGACOMPACT_DEDUP_SIM ?? "0.9"),
|
|
@@ -312,6 +323,9 @@ export function loadConfig(): MegaConfig {
|
|
|
312
323
|
recallMaxTokens: envFlag("MEGACOMPACT_RECALL_MAX_TOKENS", 1500),
|
|
313
324
|
windowDedupe: envBool("MEGACOMPACT_WINDOW_DEDUPE", true),
|
|
314
325
|
tuiWidget: envBool("MEGACOMPACT_TUI_WIDGET", true),
|
|
326
|
+
// D.1: env-overridable recompact delta (minimum context growth % before
|
|
327
|
+
// re-compacting instead of replaying the cached live trim). Default 50.
|
|
328
|
+
recompactPctDelta: envFlag("MEGACOMPACT_RECOMPACT_PCT_DELTA", 50),
|
|
315
329
|
debug: envBool("MEGACOMPACT_DEBUG", false),
|
|
316
330
|
};
|
|
317
331
|
}
|
|
@@ -8,7 +8,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
8
8
|
import type { MegaRuntime } from "../mega-runtime.js";
|
|
9
9
|
import { piCompactWouldNoop, runMemoryReview } from "../mega-pipeline.js";
|
|
10
10
|
import { memoryReviewCadence, type MegaConfig } from "../mega-config.js";
|
|
11
|
-
import { recordScore } from "../../src/store/sqlite.js";
|
|
11
|
+
import { recordScore, readLatestCacheHitPct } from "../../src/store/sqlite.js";
|
|
12
12
|
import { evaluateAndUnlockAchievements } from "../../src/store/sqlite/game-achievements.js";
|
|
13
13
|
import {
|
|
14
14
|
ensureConversationIdFor,
|
|
@@ -18,10 +18,13 @@ import { isMegaCache } from "../../src/game/scoring.js";
|
|
|
18
18
|
import { resolveRepoRoot } from "../mega-config.js";
|
|
19
19
|
import {
|
|
20
20
|
classifyError,
|
|
21
|
+
classifyErrorDetailed,
|
|
21
22
|
errorRetryBackoffMs,
|
|
22
23
|
extractErrorSignature,
|
|
24
|
+
isKnownRetryableTransient,
|
|
23
25
|
} from "./error-classifier.js";
|
|
24
26
|
import { safeSendUserMessage } from "./send-safe.js";
|
|
27
|
+
import { maybeSendProviderOutageAdvisory } from "./outage-advisor.js";
|
|
25
28
|
import { vectorStats } from "../../src/vectorStore.js";
|
|
26
29
|
|
|
27
30
|
/** Register agent/turn tracking event handlers. */
|
|
@@ -301,7 +304,10 @@ export function registerAgentHandlers(
|
|
|
301
304
|
if (runtime.getCachedGameState().game_mode_on) {
|
|
302
305
|
const repo = resolveRepoRoot(ctx.cwd) ?? runtime.currentStateDir;
|
|
303
306
|
const st = vectorStats(runtime.store, runtime.rt.sessionId);
|
|
304
|
-
|
|
307
|
+
// C.3: prefer provider cache hit rate, fall back to dedup hit rate
|
|
308
|
+
const providerPct = readLatestCacheHitPct(runtime.currentStateDir);
|
|
309
|
+
const cachePct =
|
|
310
|
+
providerPct != null ? providerPct : st.dedupHitRate * 100;
|
|
305
311
|
const modelId = runtime.currentModel?.modelId ?? "unknown";
|
|
306
312
|
recordScore(runtime.currentStateDir, {
|
|
307
313
|
repo_root: repo,
|
|
@@ -389,14 +395,17 @@ export function registerAgentHandlers(
|
|
|
389
395
|
// S28 handles; nothing for S38 to do here.
|
|
390
396
|
} else {
|
|
391
397
|
const category = classifyError(event.message);
|
|
398
|
+
const detail = classifyErrorDetailed(event.message);
|
|
392
399
|
// R3: stateful poisoned-context signal — repeated identical error text
|
|
393
400
|
// across consecutive turns. The classifier is stateless, so this
|
|
394
401
|
// upgrade happens here. A 'transient' (or 'poisoned-context') turn
|
|
395
402
|
// whose normalized error signature matches the previous one bumps
|
|
396
403
|
// errorTextRepeatCount; once it crosses the threshold, the category
|
|
397
|
-
// is upgraded to 'poisoned-context' (the request is deterministic)
|
|
398
|
-
//
|
|
399
|
-
//
|
|
404
|
+
// is upgraded to 'poisoned-context' (the request is deterministic) —
|
|
405
|
+
// R7: unless the signature carries a known-retryable network/
|
|
406
|
+
// throughput marker (guard below). Conservative: only upgrade, never
|
|
407
|
+
// downgrade, and only for transient (a 'permanent' auth error
|
|
408
|
+
// repeating is still permanent).
|
|
400
409
|
let effectiveCategory = category;
|
|
401
410
|
const errSig = extractErrorSignature(event.message);
|
|
402
411
|
if (category === "transient" || category === "poisoned-context") {
|
|
@@ -412,7 +421,25 @@ export function registerAgentHandlers(
|
|
|
412
421
|
config.poisonedContextRepeatThreshold &&
|
|
413
422
|
category === "transient"
|
|
414
423
|
) {
|
|
415
|
-
|
|
424
|
+
// R3/R7: upgrade only when the repeating error carries NO
|
|
425
|
+
// known-retryable marker. Network/throughput failures (timeout,
|
|
426
|
+
// ECONNRESET, 5xx, 429, connection lost, …) that repeat are
|
|
427
|
+
// still retryable — the API is just flaky, and /clear cannot fix
|
|
428
|
+
// them (2026-07-30 false-alarm incident). Only upgrade
|
|
429
|
+
// transient → poisoned-context for errors that repeat identically
|
|
430
|
+
// with no known-retryable cause, like the 2026-07-28 incident
|
|
431
|
+
// where the provider returned "Request failed — please retry."
|
|
432
|
+
// deterministically. The marker set is shared with classifyError
|
|
433
|
+
// (error-classifier.ts) so the two never drift.
|
|
434
|
+
if (!isKnownRetryableTransient(errSig)) {
|
|
435
|
+
effectiveCategory = "poisoned-context";
|
|
436
|
+
} else {
|
|
437
|
+
runtime.logger.info("repeat-upgrade-declined", {
|
|
438
|
+
sessionId: runtime.rt.sessionId,
|
|
439
|
+
signature: (errSig || '').slice(0, 500),
|
|
440
|
+
repeatCount: runtime.rt.errorTextRepeatCount,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
416
443
|
}
|
|
417
444
|
}
|
|
418
445
|
} else {
|
|
@@ -427,6 +454,9 @@ export function registerAgentHandlers(
|
|
|
427
454
|
runtime.rt.consecutiveErrors = 0; // S38.6: circuit-breaker reset on success
|
|
428
455
|
// R4: a successful assistant turn consumes any queued nudge.
|
|
429
456
|
runtime.rt.retryNudgePending = false;
|
|
457
|
+
// R10: reset outage advisory so a recovered-then-flapping
|
|
458
|
+
// provider re-advises once per episode.
|
|
459
|
+
runtime.rt.providerOutageAdvised = false;
|
|
430
460
|
} else if (effectiveCategory === "compaction-noop") {
|
|
431
461
|
// (4) pi race / manual compact catch — NOT retryable. The compaction
|
|
432
462
|
// already succeeded via pi's native path; retrying would race again
|
|
@@ -522,18 +552,24 @@ export function registerAgentHandlers(
|
|
|
522
552
|
runtime.rt.consecutiveErrors++; // still counts toward the circuit breaker
|
|
523
553
|
runtime.rt.poisonedCount++; // R7: dashboard counter
|
|
524
554
|
const sig = errSig || "unknown";
|
|
525
|
-
// (a) dashboard + log
|
|
555
|
+
// (a) dashboard + log — R11: include signal + rawText for forensics
|
|
526
556
|
runtime.dashboard.event("poisoned_context", {
|
|
527
557
|
signature: sig,
|
|
528
558
|
repeatCount: runtime.rt.errorTextRepeatCount,
|
|
529
559
|
turnIndex: event.turnIndex,
|
|
530
560
|
sessionId: runtime.rt.sessionId,
|
|
561
|
+
signal: detail.signal,
|
|
562
|
+
rawText: (errSig || '').slice(0, 500),
|
|
563
|
+
...(detail.httpStatus !== undefined ? { httpStatus: detail.httpStatus } : {}),
|
|
531
564
|
});
|
|
532
565
|
runtime.logger.warn("poisoned-context", {
|
|
533
566
|
sessionId: runtime.rt.sessionId,
|
|
534
567
|
turnIndex: event.turnIndex,
|
|
535
568
|
signature: sig,
|
|
536
569
|
repeatCount: runtime.rt.errorTextRepeatCount,
|
|
570
|
+
signal: detail.signal,
|
|
571
|
+
rawText: (errSig || '').slice(0, 500),
|
|
572
|
+
...(detail.httpStatus !== undefined ? { httpStatus: detail.httpStatus } : {}),
|
|
537
573
|
});
|
|
538
574
|
// (b) one-per-session advise message (throttled by poisonedAdviseSent)
|
|
539
575
|
if (!runtime.rt.poisonedAdviseSent) {
|
|
@@ -589,6 +625,11 @@ export function registerAgentHandlers(
|
|
|
589
625
|
}
|
|
590
626
|
// S38.6: circuit-breaker — stop retrying after too many consecutive errors.
|
|
591
627
|
runtime.rt.consecutiveErrors++;
|
|
628
|
+
// R10: send calm "provider outage" advisory once per episode.
|
|
629
|
+
await maybeSendProviderOutageAdvisory(
|
|
630
|
+
effectiveCategory, runtime, pi, config,
|
|
631
|
+
{ signal: detail.signal, rawText: (errSig || '').slice(0, 500) },
|
|
632
|
+
);
|
|
592
633
|
if (runtime.rt.consecutiveErrors > config.maxConsecutiveErrors) {
|
|
593
634
|
runtime.dashboard.event("error_retry_circuit_open", {
|
|
594
635
|
consecutive: runtime.rt.consecutiveErrors,
|
|
@@ -174,7 +174,12 @@ export function registerContextHandler(
|
|
|
174
174
|
const db = openStore(runtime.currentStateDir);
|
|
175
175
|
const epochId = epochIdFor(runtime.rt.sessionId);
|
|
176
176
|
for (const msg of messages) {
|
|
177
|
-
const raw = toRawTranscriptRow(
|
|
177
|
+
const raw = toRawTranscriptRow(
|
|
178
|
+
msg,
|
|
179
|
+
runtime.rt.sessionId,
|
|
180
|
+
epochId,
|
|
181
|
+
runtime.currentTurn,
|
|
182
|
+
);
|
|
178
183
|
if (raw) appendRawTranscript(db, raw);
|
|
179
184
|
}
|
|
180
185
|
} catch (e) {
|
|
@@ -214,26 +219,19 @@ export function registerContextHandler(
|
|
|
214
219
|
return;
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
runtime.debounceUntil = now + 2000;
|
|
224
|
-
|
|
222
|
+
// D.2: Replay MUST be exempt from debounce — replay is free (no compute,
|
|
223
|
+
// no re-write) and prevents unnecessary KV-cache invalidation. Check
|
|
224
|
+
// replay FIRST, before debounce, so two context events <2s apart both
|
|
225
|
+
// return the cached sentinel verbatim (re-stabilises the provider prefix).
|
|
226
|
+
//
|
|
225
227
|
// v0.8.6 cache-stability: replay the cached trim view when still in the
|
|
226
228
|
// same compaction epoch AND context hasn't grown enough to warrant a
|
|
227
|
-
// re-compact.
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
// is only valid while the transcript grows within the epoch — it is
|
|
234
|
-
// cleared on session_compact (durable truncation) + resetRuntime, so we
|
|
235
|
-
// never replay a stale cut into a truncated transcript (PREVENT-PI-001/002).
|
|
236
|
-
const RECOMPACT_PCT_DELTA = 10;
|
|
229
|
+
// re-compact. Re-compact only when context grew >=config.recompactPctDelta%
|
|
230
|
+
// of the window (percent basis) or >=50% of the effective threshold
|
|
231
|
+
// (token basis, when percent is unavailable). The cached `cut` is only
|
|
232
|
+
// valid while the transcript grows within the epoch — it is cleared on
|
|
233
|
+
// session_compact (durable truncation) + resetRuntime, so we never replay
|
|
234
|
+
// a stale cut into a truncated transcript (PREVENT-PI-001/002).
|
|
237
235
|
if (
|
|
238
236
|
runtime.trimCache &&
|
|
239
237
|
runtime.trimCache.checkpointId === runtime.rt.lastCheckpointId &&
|
|
@@ -241,7 +239,7 @@ export function registerContextHandler(
|
|
|
241
239
|
) {
|
|
242
240
|
const grewEnough =
|
|
243
241
|
pct != null && runtime.trimCache.ctxPct != null
|
|
244
|
-
? pct - runtime.trimCache.ctxPct >=
|
|
242
|
+
? pct - runtime.trimCache.ctxPct >= config.recompactPctDelta
|
|
245
243
|
: currentTokens - (runtime.trimCache.ctxTokens ?? 0) >=
|
|
246
244
|
runtime.effectiveThreshold * 0.5;
|
|
247
245
|
if (!grewEnough) {
|
|
@@ -258,6 +256,15 @@ export function registerContextHandler(
|
|
|
258
256
|
// else: context grew enough → fall through to re-compact (cache is stale)
|
|
259
257
|
}
|
|
260
258
|
|
|
259
|
+
// Debounce so we don't fire on every context event past threshold.
|
|
260
|
+
// (Replay already returned above — only fresh compacts reach this point.)
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
if (now < runtime.debounceUntil) {
|
|
263
|
+
runtime.diagCtxDebounce++;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
runtime.debounceUntil = now + 2000;
|
|
267
|
+
|
|
261
268
|
// Adaptive compression (Fix E): scale compression strength + keepFrom depth
|
|
262
269
|
// with how close we are to the model context limit. Null-safe: when the
|
|
263
270
|
// token-fallback path ran (pct unavailable) use the token-basis pressure
|
|
@@ -269,8 +276,24 @@ export function registerContextHandler(
|
|
|
269
276
|
const ran = runCompact(pi, runtime, config, ctx, messages, {
|
|
270
277
|
compressionPressure: pressure,
|
|
271
278
|
});
|
|
279
|
+
// D.3: skip paths fall back to replay instead of returning empty.
|
|
280
|
+
// If runCompact skipped and we have a valid trimCache, replay it
|
|
281
|
+
// (free stability win) — otherwise defer to the next event.
|
|
272
282
|
if (ran.skipped) {
|
|
273
283
|
runtime.diagCtxRunSkipped++;
|
|
284
|
+
if (
|
|
285
|
+
runtime.trimCache &&
|
|
286
|
+
runtime.trimCache.checkpointId === runtime.rt.lastCheckpointId &&
|
|
287
|
+
runtime.trimCache.cut <= messages.length
|
|
288
|
+
) {
|
|
289
|
+
const recent = messages.slice(runtime.trimCache.cut); // guardrails-allow PREVENT-PI-002
|
|
290
|
+
runtime.diagLiveTrimFires++;
|
|
291
|
+
runtime.diagLiveTrimReplays++;
|
|
292
|
+
runtime.snapshot(ctx);
|
|
293
|
+
return {
|
|
294
|
+
messages: [{ ...runtime.trimCache.summaryAgentMsg }, ...recent],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
274
297
|
return;
|
|
275
298
|
}
|
|
276
299
|
|
|
@@ -295,7 +318,12 @@ export function registerContextHandler(
|
|
|
295
318
|
// them (compression-by-conversation-epoch metrics). Isolated-store
|
|
296
319
|
// only; best-effort + non-fatal.
|
|
297
320
|
try {
|
|
298
|
-
stampTurnsEpochFor(
|
|
321
|
+
stampTurnsEpochFor(
|
|
322
|
+
config,
|
|
323
|
+
runtime.rt.sessionId,
|
|
324
|
+
epoch.epochId,
|
|
325
|
+
runtime.currentStateDir,
|
|
326
|
+
);
|
|
299
327
|
} catch {
|
|
300
328
|
/* non-fatal: epoch stamping never breaks compaction */
|
|
301
329
|
}
|
|
@@ -304,7 +332,10 @@ export function registerContextHandler(
|
|
|
304
332
|
// AUTO_WIKI_ENABLED; best-effort + non-fatal (never breaks compaction).
|
|
305
333
|
try {
|
|
306
334
|
if (config.autoWikiEnabled && config.turnsDbEnabled) {
|
|
307
|
-
const every = Math.max(
|
|
335
|
+
const every = Math.max(
|
|
336
|
+
1,
|
|
337
|
+
TurnsConfig.WIKI_REBUILD_EVERY_N_COMPACTS,
|
|
338
|
+
);
|
|
308
339
|
const tdb = openTurnStore(runtime.currentStateDir);
|
|
309
340
|
const n = bumpWikiCompactCounter(tdb);
|
|
310
341
|
if (n % every === 0) {
|
|
@@ -314,7 +345,9 @@ export function registerContextHandler(
|
|
|
314
345
|
restarts: 5,
|
|
315
346
|
seed: 0x9e3779b9,
|
|
316
347
|
});
|
|
317
|
-
createTopicStore(runtime.currentStateDir).replaceTopicModel(
|
|
348
|
+
createTopicStore(runtime.currentStateDir).replaceTopicModel(
|
|
349
|
+
model,
|
|
350
|
+
);
|
|
318
351
|
runtime.logger.info("wiki_rebuild", {
|
|
319
352
|
clusterCount: model.k,
|
|
320
353
|
totalChunks: model.totalChunks,
|
|
@@ -326,7 +359,9 @@ export function registerContextHandler(
|
|
|
326
359
|
}
|
|
327
360
|
}
|
|
328
361
|
} catch (wikiErr) {
|
|
329
|
-
runtime.logger.warn("wiki_rebuild_failed", {
|
|
362
|
+
runtime.logger.warn("wiki_rebuild_failed", {
|
|
363
|
+
error: String(wikiErr),
|
|
364
|
+
});
|
|
330
365
|
}
|
|
331
366
|
// S27 Task 6: Fire-and-forget dedup pipeline.
|
|
332
367
|
// Deduplicates raw_transcript rows for the compacted range.
|