pi-mega-compact 0.21.3 → 0.21.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +2 -0
  2. package/dist/extensions/mega-config.js +41 -24
  3. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +7 -0
  4. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +12 -2
  5. package/dist/extensions/mega-events/context-handler/afterCompact.js +11 -0
  6. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +8 -0
  7. package/dist/extensions/mega-events/context-handler/gateCheck.js +17 -0
  8. package/dist/extensions/mega-events/health-handler.js +12 -2
  9. package/dist/extensions/mega-events/session-handlers.js +1 -0
  10. package/dist/extensions/mega-pipeline/compact/run.js +11 -0
  11. package/dist/extensions/mega-runtime/helpers.js +46 -0
  12. package/dist/extensions/mega-runtime/internal-errors.js +14 -0
  13. package/dist/extensions/mega-runtime/pressure-getters.js +12 -10
  14. package/dist/extensions/mega-runtime/reset-runtime.js +3 -1
  15. package/dist/extensions/mega-runtime/runtime-instrumentation.js +171 -0
  16. package/dist/extensions/mega-runtime/runtime.js +27 -193
  17. package/dist/extensions/mega-runtime/state.js +1 -1
  18. package/dist/extensions/mega-runtime/widget.js +1 -1
  19. package/dist/extensions/mega-turn-store.js +5 -0
  20. package/dist/src/bridge/factory.js +15 -1
  21. package/dist/src/compact-summary.js +99 -0
  22. package/dist/src/compact.js +15 -99
  23. package/dist/src/contextHealth.js +13 -5
  24. package/dist/src/metrics/turns.js +6 -1
  25. package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
  26. package/dist/src/store/sqlite/context-health.js +6 -48
  27. package/dist/src/store/sqlite/embedding-cache.js +4 -0
  28. package/dist/src/store/sqlite/schema.js +4 -0
  29. package/dist/src/store/turns/memory-store/admin.js +119 -0
  30. package/dist/src/store/turns/memory-store/ctx.js +28 -0
  31. package/dist/src/store/turns/memory-store/read.js +110 -0
  32. package/dist/src/store/turns/memory-store/write.js +88 -0
  33. package/dist/src/store/turns/memory-store.js +44 -273
  34. package/dist/src/store/turns/migrations.js +4 -0
  35. package/dist/src/store/turns/schema.js +1 -0
  36. package/dist/src/store/turns/sqlite-store/read.js +13 -0
  37. package/dist/src/store/turns/sqlite-store/rows.js +4 -0
  38. package/dist/src/store/turns/sqlite-store/write.js +3 -3
  39. package/dist/src/store/turns/sqlite-store.js +4 -0
  40. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
  54. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
  61. package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
  75. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
  77. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
  83. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
  84. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
  85. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
  86. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
  87. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
  88. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
  89. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
  91. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
  92. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
  98. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
  99. package/extensions/dashboard-client/dist/index.html +1 -1
  100. package/extensions/dashboard-client/src/api/health.ts +1 -0
  101. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
  102. package/extensions/mega-config-types.ts +8 -0
  103. package/extensions/mega-config.ts +51 -27
  104. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +7 -0
  105. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
  106. package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
  107. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
  108. package/extensions/mega-events/context-handler/gateCheck.ts +18 -0
  109. package/extensions/mega-events/health-handler.ts +13 -3
  110. package/extensions/mega-events/session-handlers.ts +1 -0
  111. package/extensions/mega-pipeline/compact/run.ts +11 -0
  112. package/extensions/mega-runtime/helpers.ts +51 -0
  113. package/extensions/mega-runtime/internal-errors.ts +30 -0
  114. package/extensions/mega-runtime/pressure-getters.ts +12 -12
  115. package/extensions/mega-runtime/reset-runtime.ts +3 -1
  116. package/extensions/mega-runtime/runtime-instrumentation.ts +200 -0
  117. package/extensions/mega-runtime/runtime.ts +29 -209
  118. package/extensions/mega-runtime/state.ts +1 -1
  119. package/extensions/mega-runtime/widget.ts +1 -1
  120. package/extensions/mega-turn-store.ts +9 -0
  121. package/package.json +2 -2
  122. package/src/bridge/factory.ts +15 -1
  123. package/src/bridge/types.ts +2 -0
  124. package/src/compact-summary.ts +120 -0
  125. package/src/compact.ts +19 -109
  126. package/src/contextHealth.ts +26 -6
  127. package/src/metrics/turns.ts +6 -1
  128. package/src/store/sqlite/context-health-cache-poison.ts +97 -0
  129. package/src/store/sqlite/context-health.ts +12 -84
  130. package/src/store/sqlite/embedding-cache.ts +4 -0
  131. package/src/store/sqlite/schema.ts +4 -0
  132. package/src/store/turns/memory-store/admin.ts +171 -0
  133. package/src/store/turns/memory-store/ctx.ts +62 -0
  134. package/src/store/turns/memory-store/read.ts +172 -0
  135. package/src/store/turns/memory-store/write.ts +126 -0
  136. package/src/store/turns/memory-store.ts +51 -330
  137. package/src/store/turns/migrations.ts +4 -0
  138. package/src/store/turns/schema.ts +1 -0
  139. package/src/store/turns/sqlite-store/read.ts +19 -0
  140. package/src/store/turns/sqlite-store/rows.ts +5 -0
  141. package/src/store/turns/sqlite-store/write.ts +3 -2
  142. package/src/store/turns/sqlite-store.ts +5 -0
  143. package/src/store/turns/types.ts +15 -0
  144. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
  145. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
  146. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
  147. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import type { MegaRuntime } from "../../../mega-runtime.js";
10
10
  import type { MegaConfig } from "../../../mega-config.js";
11
- import { ensureConversationIdFor, recordTurnWrite } from "../../../mega-turn-store.js";
11
+ import { ensureConversationIdFor, recordTurnWrite, turnReaderFor } from "../../../mega-turn-store.js";
12
12
  import type { TurnEndEvent } from "./event.js";
13
13
 
14
14
  /** S43: record one turn row with the cached metrics. Best-effort + non-fatal. */
@@ -28,12 +28,18 @@ export function recordTurnRow(
28
28
  runtime.rt.sessionId,
29
29
  runtime.currentStateDir,
30
30
  );
31
+ // S49R: store the conversation-monotonic index (MAX+1) instead of the
32
+ // per-session event.turnIndex, which restarts at 0 on resume and would
33
+ // collide with UNIQUE(conversation_id, turn_index). Carry the session
34
+ // counter as sessionTurnIndex for the raw_transcript metrics join.
35
+ const turnIndex = turnReaderFor(runtime.currentStateDir).nextTurnIndexFor(convId);
31
36
  recordTurnWrite(
32
37
  config,
33
38
  {
34
39
  conversationId: convId,
35
40
  sessionId: runtime.rt.sessionId,
36
- turnIndex: event.turnIndex,
41
+ turnIndex,
42
+ sessionTurnIndex: event.turnIndex,
37
43
  role: (event as { role?: string }).role ?? "assistant",
38
44
  endedAt: Date.now(),
39
45
  startedAt: undefined,
@@ -57,5 +63,9 @@ export function recordTurnRow(
57
63
  error: e instanceof Error ? e.message : String(e),
58
64
  stack: e instanceof Error ? e.stack?.split("\n").slice(0, 3).join(" | ") : undefined,
59
65
  });
66
+ // Sprint H (Finding 3 / Option A): the turn-row write is an internal
67
+ // store-write failure the API-error `errorRate` ring never sees. Feed the
68
+ // separate `storeErrorRate` 6th axis.
69
+ runtime.recordInternalError("store_write");
60
70
  }
61
71
  }
@@ -77,6 +77,8 @@ export async function persistEpochAndMaintain(
77
77
  writeCheckpointEpoch(db, epoch);
78
78
  } catch (e) {
79
79
  runtime.logger.warn("planv2-epoch-fail", { error: String(e) });
80
+ // Sprint H (Option A): checkpoint_epoch write failure.
81
+ runtime.recordInternalError("store_write");
80
82
  }
81
83
  }
82
84
 
@@ -181,6 +183,9 @@ export async function persistEpochAndMaintain(
181
183
  runtime.logger.warn("wiki_rebuild_failed", {
182
184
  error: String(wikiErr),
183
185
  });
186
+ // Sprint H (Option A): wiki rebuild writes (topic model / overrides)
187
+ // to the isolated store — internal store write.
188
+ runtime.recordInternalError("store_write");
184
189
  }
185
190
 
186
191
  // D1: seed the topic model from raw_transcript when context_chunks is
@@ -263,6 +268,9 @@ export async function persistEpochAndMaintain(
263
268
  runtime.logger.warn("wiki_seed_failed", {
264
269
  error: String(seedErr),
265
270
  });
271
+ // Sprint H (Option A): wiki seed rebuild writes to the isolated
272
+ // store — internal store write.
273
+ runtime.recordInternalError("store_write");
266
274
  }
267
275
 
268
276
  // S27 Task 6: Fire-and-forget dedup pipeline.
@@ -280,6 +288,9 @@ export async function persistEpochAndMaintain(
280
288
  }
281
289
  } catch (e) {
282
290
  runtime.logger.warn("db-mirror-epoch-fail", { error: String(e) });
291
+ // Sprint H (Option A): db-mirror downstream maintenance writes
292
+ // (stamp turns epoch / wiki / seed / dedup) — internal store writes.
293
+ runtime.recordInternalError("store_write");
283
294
  }
284
295
  }
285
296
 
@@ -105,6 +105,9 @@ export function appendMirrorAndLedger(
105
105
  }
106
106
  } catch (e) {
107
107
  runtime.logger.warn("db-mirror-append-fail", { error: String(e) });
108
+ // Sprint H (Option A): raw_transcript / thread / tool_results write
109
+ // failure — internal store write. Feed the `storeErrorRate` axis.
110
+ runtime.recordInternalError("store_write");
108
111
  }
109
112
  }
110
113
 
@@ -121,5 +124,10 @@ export function appendMirrorAndLedger(
121
124
  );
122
125
  } catch (e) {
123
126
  runtime.logger.warn("vc1b-ledger-append-fail", { error: String(e) });
127
+ // Sprint H (Option A): the v2 vector-cortex ledger append is a cortex
128
+ // write — this is the host-side hook for the cortex seam (the cortex
129
+ // contract forbids the store from holding a runtime handle, so the ring
130
+ // push happens here in the adapter, not inside src/vector-cortex).
131
+ runtime.recordInternalError("vector_index");
124
132
  }
125
133
  }
@@ -95,6 +95,24 @@ export function evaluateGate(
95
95
  stateDir: runtime.currentStateDir,
96
96
  });
97
97
 
98
+ // Phase H: output-error catch. A truncated model output (S28
99
+ // stopReason==='length' — "Response was truncated before completion") trips
100
+ // a one-shot force-compact so the next compaction runs IMMEDIATELY, freeing
101
+ // input headroom for the model's next response. This closes the
102
+ // small-context-model deadlock: the model truncates MID-OUTPUT below the
103
+ // 80% INPUT threshold (so the gate never fires) → "compact never" → every
104
+ // subsequent response also truncates. lengthStop.ts sets this flag
105
+ // alongside its auto-continue nudge; gated by config.outputErrorCompact
106
+ // (default ON; OFF = byte-identical pre-H). One-shot: cleared on consumption.
107
+ // thrashGuardBlocks is consulted separately by the handler; in the reported
108
+ // "compact never" case compactCount===0 so the guard is never armed and will
109
+ // not block this trip.
110
+ if (config.outputErrorCompact && runtime.rt?.forceCompactNextGate) {
111
+ runtime.rt.forceCompactNextGate = false;
112
+ runtime.diagCtxOutputErrorTrip++;
113
+ return { kind: "proceed", perModelThreshold };
114
+ }
115
+
98
116
  // S29 FAST GATE: `custom` (absolute MEGACOMPACT_THRESHOLD_TOKENS,
99
117
  // tierPct null) is an explicit opt-out of percent scaling — it keeps the
100
118
  // token gate. When pct is unavailable (window unknown / a model that
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { MegaRuntime } from "../mega-runtime.js";
13
+ import { RING_MAX } from "../mega-runtime/runtime.js";
13
14
  import type { MegaConfig } from "../mega-config.js";
14
15
  import { defaultEmbedder } from "../../src/embedder.js";
15
16
  import { computeOutputQuality } from "../../src/contextHealth/outputQuality.js";
@@ -34,8 +35,6 @@ import {
34
35
  } from "../../src/store/sqlite/context-health.js";
35
36
  import { getHealthMitigate } from "../../src/store/sqlite/meta.js";
36
37
 
37
- const RING_MAX = 5;
38
-
39
38
  /** Extract assistant text from the turn_end event message. */
40
39
  function extractAssistantText(event: { message?: { role?: string; content?: unknown } }): string {
41
40
  const msg = event.message;
@@ -108,9 +107,17 @@ export function handleTurnEndHealth(
108
107
  drift = computeDriftScore(topic, error, prefix);
109
108
  }
110
109
 
111
- // Error rate sub-score (1 = no errors)
110
+ // Error rate sub-score (1 = no errors) — API-retry errors only.
112
111
  const errorScore = computeErrorEscalation(runtime.recentErrorCategories);
113
112
 
113
+ // Sprint H (B2): store-error sub-score (1 = no store-write failures).
114
+ // Distinct from errorScore — internal store/service-write failures
115
+ // (turn rows, recall, dbMirror, ledger, epoch, stats, memory consolidate,
116
+ // compact persist, vector index, embedder, migration) never reach the
117
+ // API-retry errorRate ring. Mirrors computeErrorEscalation: 1 - (n/RING_MAX).
118
+ const recentInternalCount = Math.min(runtime.recentInternalErrors.length, RING_MAX);
119
+ const storeErrorScore = 1 - recentInternalCount / RING_MAX;
120
+
114
121
  // Cache health (from perf-handler)
115
122
  const cacheHealth = runtime.rt._lastCacheHealthScore ?? 1.0;
116
123
 
@@ -154,6 +161,7 @@ export function handleTurnEndHealth(
154
161
  errorRate: errorScore,
155
162
  cacheHealth,
156
163
  cachePoison,
164
+ storeErrorRate: storeErrorScore,
157
165
  };
158
166
  const composite = computeHealthScore(sub);
159
167
 
@@ -168,6 +176,7 @@ export function handleTurnEndHealth(
168
176
  cacheHealth,
169
177
  cachePoison,
170
178
  composite,
179
+ storeErrorScore,
171
180
  modelId: runtime.currentModel?.modelId,
172
181
  repetitionRatio,
173
182
  coherenceScore,
@@ -180,6 +189,7 @@ export function handleTurnEndHealth(
180
189
  drift,
181
190
  outputQuality,
182
191
  errorRate: errorScore,
192
+ storeErrorRate: storeErrorScore,
183
193
  cacheHealth,
184
194
  cachePoison,
185
195
  turnIndex: event.turnIndex,
@@ -111,6 +111,7 @@ export function registerSessionHandlers(
111
111
  runtime.logger.info("db-auto-maintain", { result: m });
112
112
  } catch (e) {
113
113
  runtime.logger.warn("db-auto-maintain-fail", { error: String(e) });
114
+ runtime.recordInternalError("store_write");
114
115
  }
115
116
  runtime.dashboard.event("session_start", {
116
117
  reason: event.reason,
@@ -166,6 +166,9 @@ function doCompact(
166
166
  touchSession(sid, root, runtime.currentStateDir);
167
167
  logDaily(sid, "compact", result.checkpointId, saved, runtime.currentStateDir);
168
168
  } catch {
169
+ // Sprint H (Option A): session-activity / daily-log write is an internal
170
+ // store-write failure — feed the separate `storeErrorRate` axis.
171
+ runtime.recordInternalError("store_write");
169
172
  /* non-fatal: stats bookkeeping only */
170
173
  }
171
174
 
@@ -183,10 +186,14 @@ function doCompact(
183
186
  if (n > 0) runtime.pushTicker(`${C.green}∫${C.reset} consolidated ${n} memory dup${n === 1 ? "" : "s"}`);
184
187
  },
185
188
  () => {
189
+ // Sprint H (Option A): memory-consolidation write failure.
190
+ runtime.recordInternalError("store_write");
186
191
  /* swallow: consolidate failures must never surface to the user */
187
192
  },
188
193
  );
189
194
  } catch {
195
+ // Sprint H (Option A): memory-consolidation write failure (sync throw).
196
+ runtime.recordInternalError("store_write");
190
197
  /* non-fatal */
191
198
  }
192
199
  }
@@ -259,6 +266,8 @@ function doCompact(
259
266
  }
260
267
  }
261
268
  } catch {
269
+ // Sprint H (Option A): RAPTOR tree-refresh write failure.
270
+ runtime.recordInternalError("vector_index");
262
271
  /* non-fatal: tree refresh never blocks a compaction */
263
272
  }
264
273
  }
@@ -278,6 +287,8 @@ function doCompact(
278
287
  latest.checkpointId,
279
288
  latest.embedding,
280
289
  ).catch(() => {
290
+ // Sprint H (Option A): async global vector-index upsert failure.
291
+ runtime.recordInternalError("vector_index");
281
292
  /* non-fatal: index refresh never blocks a compaction */
282
293
  });
283
294
  }
@@ -9,6 +9,7 @@
9
9
  import { join, dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { readFileSync } from "node:fs";
12
+ import { normalizeSessionId } from "../../src/store.js";
12
13
 
13
14
  // ── Public string constants ────────────────────────────────────────────────
14
15
  // Exported via the barrel — consumers (mega-events.ts, mega-pipeline.ts) use
@@ -50,6 +51,10 @@ export interface SessionRuntime {
50
51
  recallInjections: number; // recall blocks injected this session-instance
51
52
  cacheHitTokens: number; // tokens saved via cache hits (dedup + recall) this session
52
53
  lengthStopPending: boolean; // S28: set on turn_end when stopReason==='length'
54
+ /** Phase H: one-shot flag set alongside lengthStopPending (S28) so the next
55
+ * compaction gate trips immediately regardless of input-pressure. Cleared on
56
+ * consumption by evaluateGate (gateCheck.ts). See config.outputErrorCompact. */
57
+ forceCompactNextGate: boolean;
53
58
  errorRetryCount: number; // S38: consecutive error turns, reset on success/turn_start
54
59
  errorRetryUntil: number; // S38: wall-clock ms before which the next nudge is suppressed (R1: now gating)
55
60
  // S38.6: circuit-breaker state — consecutive error turns across the session.
@@ -111,3 +116,49 @@ export function ownVersion(): string {
111
116
  CACHED_VERSION = v;
112
117
  return v;
113
118
  }
119
+
120
+ /**
121
+ * createSessionRuntime() — the default per-session runtime state.
122
+ *
123
+ * Extracted from runtime.ts (delegate-shell split) so the MegaRuntime class
124
+ * stays a field-declarations + 1-line-delegate shell. Returns a fresh
125
+ * SessionRuntime for each session (reset on session_start / session_tree).
126
+ * Mirrors the neuralwatt-mcr per-session closure.
127
+ */
128
+ export function createSessionRuntime(): SessionRuntime {
129
+ return {
130
+ sessionId: normalizeSessionId(undefined),
131
+ persistedThisSession: false,
132
+ lastCheckpointId: undefined,
133
+ lastCompactedFrom: 0,
134
+ lastCompactedTokens: 0,
135
+ dedupSkips: 0,
136
+ dedupAttempts: 0,
137
+ tokensSaved: 0,
138
+ lastCompactAt: null,
139
+ lastRecallAt: null,
140
+ lastInjectAt: null,
141
+ _prevCacheHitPct: null,
142
+ _lastCacheHealthScore: undefined,
143
+ lastNativeCompactAt: null,
144
+ compactCount: 0,
145
+ recallInjections: 0,
146
+ cacheHitTokens: 0,
147
+ lengthStopPending: false,
148
+ forceCompactNextGate: false,
149
+ errorRetryCount: 0,
150
+ errorRetryUntil: 0,
151
+ consecutiveErrors: 0,
152
+ lastErrorRetryAt: 0,
153
+ retryNudgePending: false,
154
+ errorRetrySessionCount: 0,
155
+ lastErrorText: undefined,
156
+ errorTextRepeatCount: 0,
157
+ poisonedAdviseSent: false,
158
+ providerOutageAdvised: false,
159
+ poisonedCompactSignatures: new Set(),
160
+ recallInjectedThisTurn: false,
161
+ poisonedCount: 0,
162
+ extensionInitiatedTurn: false,
163
+ };
164
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * internal-errors.ts — Sprint H (Finding 3 / Option A) instrumentation.
3
+ *
4
+ * Free-function + context-interface sibling of `runtime.ts` (delegate-shell
5
+ * pattern). Holds the body of `recordInternalError(category)` so the
6
+ * `MegaRuntime` class stays a field-declarations + 1-line-delegate shell.
7
+ *
8
+ * Pushes a store/service-write failure category into the `recentInternalErrors`
9
+ * ring (capped at RING_MAX, shift when over). Called AT each failure emit site
10
+ * (see the §2.3a audit in docs/specs/c2-resume-and-health-fixes.md) — never by
11
+ * a central events.log filter. Feeds the distinct `storeErrorRate` 6th health
12
+ * axis (committed separately in Sprint H axis work).
13
+ */
14
+ import type { MegaRuntime } from "./runtime.js";
15
+
16
+ /**
17
+ * RING_MAX — the cap for the rolling health ring buffers
18
+ * (`recentTurnEmbeddings`, `recentErrorCategories`, `recentInternalErrors`).
19
+ * Defined here (with the ring writer) and re-exported from runtime.ts so the
20
+ * ring + its writer live together; health-handler.ts imports it.
21
+ */
22
+ export const RING_MAX = 5;
23
+
24
+ /** Push a category into the internal-error ring. Mirrors `recentErrorCategories`. */
25
+ export function recordInternalErrorImpl(rt: MegaRuntime, category: string): void {
26
+ rt.recentInternalErrors.push(category);
27
+ if (rt.recentInternalErrors.length > RING_MAX) {
28
+ rt.recentInternalErrors.shift();
29
+ }
30
+ }
@@ -81,18 +81,18 @@ export function pressureImpl(self: PressureContext): number {
81
81
  * always below pi's native auto-compaction (~80% of window).
82
82
  */
83
83
  export function effectiveThresholdImpl(self: PressureContext): number {
84
- // 3WF-2 threshold invariant: under the umbrella, a tiered config with an
85
- // UNKNOWN window (lastCtxWindow <= 0) DEFERS — auto-compaction must never
86
- // substitute a guessed window. Returning +Infinity keeps every downstream
87
- // `tokens >= threshold` comparison false (gateCheck token path,
88
- // agent_end durable trigger, live-trim re-compact), so no compaction fires
89
- // until the provider reports a real window. custom (tierPct null) and
90
- // umbrella-OFF fall through to the legacy helper (byte-identical).
91
- if (
92
- self.config.threeWayFailback &&
93
- self.config.tierPct != null &&
94
- self.lastCtxWindow <= 0
95
- ) {
84
+ // 3WF-2 threshold invariant (LTS-correctness fix, Phase C): a tiered config
85
+ // (tierPct != null) with an UNKNOWN window (lastCtxWindow <= 0) DEFERS —
86
+ // auto-compaction must never substitute a guessed window, under ANY umbrella
87
+ // state. Returning +Infinity keeps every downstream `tokens >= threshold`
88
+ // comparison false (gateCheck token path, agent_end durable trigger, live-trim
89
+ // re-compact), so no compaction fires until the provider reports a real
90
+ // window. This closes the small-context-model deadlock: umbrella-OFF
91
+ // previously fell through to the legacy 200k helper (round(tierPct × 200k)),
92
+ // which is unreachable on a 32k window → the model truncates before the gate
93
+ // ever fires → "compact never". custom (tierPct null) always falls through
94
+ // to the explicit absolute.
95
+ if (self.config.tierPct != null && self.lastCtxWindow <= 0) {
96
96
  return Number.POSITIVE_INFINITY;
97
97
  }
98
98
  return effectiveThresholdTokens({
@@ -13,6 +13,7 @@ import { normalizeSessionId } from "../../src/store.js";
13
13
  import type { TickerEntry } from "./widget.js";
14
14
  import type { GameState } from "../../src/store/sqlite.js";
15
15
  import type { SessionRuntime } from "./helpers.js";
16
+ import { DEFAULT_SAVED_GOAL } from "../mega-config.js";
16
17
 
17
18
  // ---------------------------------------------------------------------- types
18
19
 
@@ -67,6 +68,7 @@ export function resetRuntimeImpl(
67
68
  recallInjections: 0,
68
69
  cacheHitTokens: 0,
69
70
  lengthStopPending: false,
71
+ forceCompactNextGate: false,
70
72
  errorRetryCount: 0,
71
73
  errorRetryUntil: 0,
72
74
  consecutiveErrors: 0,
@@ -90,7 +92,7 @@ export function resetRuntimeImpl(
90
92
  self.tierTrace = undefined;
91
93
  self.ticker.length = 0;
92
94
  self.pulsing = false;
93
- self.savedGoal = 50_000;
95
+ self.savedGoal = DEFAULT_SAVED_GOAL;
94
96
  self.lastWhy = undefined;
95
97
  // S31 audit P2: symmetry with bindRepo — a reset can coincide with a context
96
98
  // that re-binds the repo, so drop the memo too. Cheap; the next
@@ -0,0 +1,200 @@
1
+ /**
2
+ * runtime-instrumentation.ts — base class holding MegaRuntime's field
3
+ * declarations (diagnostic counters + context-health ring buffers + the Sprint-H
4
+ * internal-error ring + live display/cache-stability state).
5
+ *
6
+ * Extracted from runtime.ts (delegate-shell split) so the MegaRuntime class
7
+ * stays a field-declarations + 1-line-delegate shell. As a base class, the
8
+ * field NAMES are unchanged (`this.diagLiveTrimFires`, `this.trimCache`, etc.) —
9
+ * call sites across the codebase and tests need no edits.
10
+ */
11
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
12
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
13
+ import type { ModelSnapshot, GameState } from "../../src/store/sqlite.js";
14
+ import type { TickerEntry, WidgetData } from "./widget.js";
15
+ import type { FSWatcher } from "node:fs";
16
+ import { DEFAULT_SAVED_GOAL } from "../mega-config.js";
17
+
18
+ /**
19
+ * DIAG counters for the "team run doesn't relieve context" investigation.
20
+ * Plain integers, incremented at the three compaction decision points. They
21
+ * let a headless test drive the real event handlers and assert the firing
22
+ * cadence without scraping log files. Inert in production (the live-trim and
23
+ * before-compact probes also emit logger.info, but these counters are always
24
+ * updated and cost nothing).
25
+ */
26
+ export class RuntimeInstrumentation {
27
+ diagLiveTrimFires = 0; // context handler returned a trimmed view
28
+ diagLiveTrimReplays = 0; // v0.8.6: trim view returned via cached replay (skipped re-compact)
29
+ diagBeforeCompactFires = 0; // session_before_compact handler entered
30
+ diagBeforeCompactSupplied = 0; // session_before_compact supplied our trim
31
+ diagAgentEndIdle = 0; // agent_end with activeAgents===0
32
+ diagAgentEndDurable = 0; // agent_end fired ctx.compact() (mid-run durable trim)
33
+ diagAgentEndDurableSkipRecent = 0; // agent_end skipped ctx.compact() — compaction in last 10s (race guard)
34
+ // Per-skip-path counters for the team-run diagnosis.
35
+ diagCtxFastGate = 0; // returned at token fast-gate (below threshold)
36
+ diagCtxNoCompact = 0; // autoCompactCheck().shouldCompact === false
37
+ diagCtxDebounce = 0; // debounceUntil not yet elapsed
38
+ diagCtxRunSkipped = 0; // runCompact() returned skipped
39
+ diagCtxCutNull = 0; // computeLiveTrimCut returned null (anchor/boundary)
40
+ diagCtxThrown = 0; // live-trim try threw (caught)
41
+ diagCtxOutputErrorTrip = 0; // Phase H: output-error catch tripped a forced compaction
42
+
43
+ // Context health instrumentation (v0.12): rolling ring buffers for
44
+ // drift detection + cache poison Layer 1 hash baseline.
45
+ recentTurnEmbeddings: number[][] = [];
46
+ recentErrorCategories: (string | null)[] = [];
47
+ lastPrefixHash: string | null = null;
48
+ lastErrorCategory: string | null = null;
49
+
50
+ // Sprint H (Finding 3): a SEPARATE internal-error ring fed by explicit
51
+ // `recordInternalError(category)` calls at each store/service-write failure
52
+ // emit site (Option A — NOT a central events.log filter). This captures
53
+ // internal store-write failures which the API-error `errorRate` ring never
54
+ // sees. The health-handler computes a distinct `storeErrorRate` 6th axis.
55
+ // Mirrors `recentErrorCategories` / `lastErrorCategory` exactly (cap = RING_MAX).
56
+ recentInternalErrors: string[] = [];
57
+
58
+ /**
59
+ * S26 capture instrumentation: the "model_snapshots empty → $0.00 cost card"
60
+ * bug was invisible because captureModel swallowed the DB write in a silent
61
+ * `catch {}`. These always-updated counters (zero cost) let a headless test or
62
+ * a live capture tell whether captureModel ran and whether the snapshot landed.
63
+ */
64
+ diagCaptureModelCalls = 0; // captureModel entered with a populated ctx.model
65
+ diagCaptureModelFails = 0; // recordModelSnapshot threw → model_snapshots stays empty
66
+
67
+ // ── Live display / cache-stability state (moved from runtime.ts shell) ────
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: {
77
+ checkpointId: string;
78
+ cut: number;
79
+ summaryAgentMsg: AgentMessage;
80
+ ctxPct: number | null;
81
+ ctxTokens: number | null;
82
+ } | null = null;
83
+ debounceUntil = 0;
84
+ // S16: debounce for the agent_end resume nudge (avoid busy-loops).
85
+ resumeNudgeUntil = 0;
86
+ // Agent tracking for real-time widget updates
87
+ activeAgents = 0;
88
+ currentTurn = 0;
89
+ // S33: transient MEGA CACHE flare flag (armed by the turn_end scoring hook
90
+ // when cachePct > 100). Copied into widgetData.megaCacheFlare on the next
91
+ // snapshot() so the widget renders the oopsie gag, then reset (one cycle).
92
+ megaCacheFlare = false;
93
+ /** v0.8.3: ambient effect state for animated panel borders keyed off
94
+ * status transitions (level-up, mega-cache overshoot, achievement unlock,
95
+ * compaction start). Threaded into widgetData as `activeEffect`; the widget
96
+ * computes the per-frame phase from startedAt vs Date.now() (non-expired).
97
+ * Null when idle/expired. */
98
+ activeEffect: { type: "pulse" | "flash"; role: "accent" | "mega" | "red"; startedAt: number; durationMs: number } | null = null;
99
+ megaCacheFlarePct = 0;
100
+ levelUpFlare = false;
101
+ lastLevel = 0;
102
+ // S35: transient achievement-unlock flare (armed by the scoring hooks after
103
+ // evaluateAndUnlockAchievements returns newly-unlocked titles). Copied into
104
+ // widgetData.achievementFlare on the next snapshot() so the widget renders the
105
+ // unlock toast, then reset (one cycle — mirrors megaCacheFlare/levelUpFlare).
106
+ achievementFlare = false;
107
+ achievementFlareTitles: string[] = [];
108
+ // S33: last cumulative dedup-collapsed count seen by the session_compact
109
+ // hook, so we only record the DELTA as the dedupe score (leaderboard sums).
110
+ lastDedupCollapsed = 0;
111
+ // Recall block produced by auto-inline (resume/branch) that the next
112
+ // before_agent_start should prepend to the system prompt. Unset after use.
113
+ pendingRecallBlock: string | undefined;
114
+ // S21: memory recall block, parallel to pendingRecallBlock. Same one-shot
115
+ // semantics; composed with the checkpoint block in before_agent_start.
116
+ pendingMemoryRecallBlock: string | undefined;
117
+ statusKey: string | undefined; // current status text for dashboard
118
+ // Active model/provider (for real cost estimation). Captured from ctx.model
119
+ // on model_select + session_start; persisted to SQL so cost + the dashboard
120
+ // can read it without a live ctx.
121
+ currentModel: ModelSnapshot | undefined;
122
+ // Live "what it's doing right now" timestamp, used for the fresh-window.
123
+ lastActivityAt = 0;
124
+ // Live per-tier dedup trace (Phase 1): e.g. "L0 ✓ → L1 ✓ → L2 0.91 → stored".
125
+ // Built from the store's sync onTier callback during a compaction so the user
126
+ // watches each tier evaluate in real time. Cleared once the outcome settles.
127
+ tierTrace: string | undefined;
128
+ // Phase 3 — standout toolbar state.
129
+ // Recall/activity ticker: a small ring buffer (≤5) of recent compact/recall
130
+ // events so the widget shows a live history instead of a single last action.
131
+ ticker: TickerEntry[] = [];
132
+ readonly TICKER_MAX = 5;
133
+ // Pulsing status: set true while a compaction is in flight, cleared on result.
134
+ pulsing = false;
135
+ // S21.2: set by `applyMemoryOps` when a memory add/replace/remove lands in
136
+ // the current compaction. The pipeline reads this after a successful compact
137
+ // to decide whether to fire `consolidateMemories` (skip the work entirely
138
+ // when no memory rows changed).
139
+ memoriesTouchedThisCompaction = 0;
140
+ // Rolling "saved" goal for the progress bar — grows as we save more, so the
141
+ // bar always has a meaningful denominator (never sits at 100% forever).
142
+ // Seeded from the %-derived DEFAULT_SAVED_GOAL (low-tier fire point at the
143
+ // default context window); resetRuntime re-seeds from the live
144
+ // effectiveThreshold when the window is known. Display-only.
145
+ savedGoal = DEFAULT_SAVED_GOAL;
146
+ // Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
147
+ // while fresh.
148
+ lastWhy: string | undefined = undefined;
149
+ // v0.8.8 Perf dashboard instrumentation: turn/provider start timestamps +
150
+ // the 5s cpu/mem interval handle (one per MegaRuntime, cleared in dispose()).
151
+ perfTurnStart = 0;
152
+ perfProviderStart = 0;
153
+ perfCpuInterval: ReturnType<typeof setInterval> | null = null;
154
+ perfCpuBaseline: { user: number; sys: number } | undefined;
155
+
156
+ // Context tracking for the dashboard (updated in the context handler).
157
+ lastCtxTokens: number | null = null;
158
+ lastCtxPercent: number | null = null;
159
+ lastCtxWindow = 0;
160
+
161
+ // Latest computed widget payload (recomputed per snapshot, rendered per frame).
162
+ widgetData: WidgetData | null = null;
163
+ // v0.8.5: material-change signature from the last full snapshot() body. When
164
+ // the next snapshot()'s signature matches, the expensive recompute (6 sync
165
+ // SQLite opens) + writeFileSync(dashboard.json) are skipped — only the
166
+ // (already-registered) widget factory is refreshed. Kills the per-event
167
+ // main-thread block during typing/idle streaming with no material change.
168
+ lastSnapshotSig: string | null = null;
169
+ lastHeartbeatAt: number = 0;
170
+ // v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
171
+ // for in-process /mega-game writes, the fs.watch callback for cross-process
172
+ // dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
173
+ // invalidates and the widget re-reads theme/mode after the change.
174
+ gameStateBump = 0;
175
+ // Cached cross-repo drift status (recomputed at most every 30s — it opens the
176
+ // machine-wide registry DB, so we don't want to do it on every render frame).
177
+ driftCache: { at: number; status: "ok" | "warn" } | null = null;
178
+ // S31: cached game-mode state (game_mode_on/theme/tui_display_mode). Lazily
179
+ // read from the game_state SQLite row on the first widget render, then
180
+ // memoized until bumpGameState() evicts it (called by /mega-game after a
181
+ // write) so the widget picks up theme/mode/level changes live without
182
+ // re-querying the DB on every render frame.
183
+ cachedGameState: GameState | undefined;
184
+ // S32: fs.watch on the current repo's sqlite.db so cross-process writes
185
+ // (e.g. the dashboard server's PUT /api/game-state, which runs as a detached
186
+ // child with no MegaRuntime ref) evict the cached game-state memo. Without
187
+ // this, /mega-game's in-process bumpGameState() is the only eviction trigger
188
+ // and the widget would keep showing stale theme/mode/toggle after a dashboard
189
+ // edit until a restart. The watcher tracks currentStateDir — closed + re-opened
190
+ // by ensureGameStateWatcher() on every bindRepo repo switch. Non-fatal: any
191
+ // fs.watch failure (missing file / platform issue) is swallowed; the next
192
+ // getCachedGameState() snapshot re-queries the DB anyway.
193
+ gameStateWatcher?: FSWatcher;
194
+ gameStateWatchDir?: string;
195
+ // P2: the last ExtensionContext handed to snapshot()/renderWidget(), stashed
196
+ // so the fs.watch game-state callback can force a widget re-render without
197
+ // a context event (cross-process dashboard edits while pi is idle). Cleared
198
+ // implicitly on construction (undefined → watcher skips until first snap).
199
+ lastWidgetCtx?: ExtensionContext;
200
+ }