pi-mega-compact 0.21.2 → 0.21.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.
Files changed (141) hide show
  1. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +1 -0
  2. package/dist/extensions/mega-compact-child.js +126 -0
  3. package/dist/extensions/mega-config.js +5 -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/health-handler.js +12 -2
  8. package/dist/extensions/mega-events/session-handlers.js +1 -0
  9. package/dist/extensions/mega-pipeline/compact/run.js +11 -0
  10. package/dist/extensions/mega-runtime/helpers.js +45 -0
  11. package/dist/extensions/mega-runtime/internal-errors.js +14 -0
  12. package/dist/extensions/mega-runtime/runtime-instrumentation.js +166 -0
  13. package/dist/extensions/mega-runtime/runtime.js +27 -193
  14. package/dist/extensions/mega-runtime/state.js +1 -1
  15. package/dist/extensions/mega-runtime/widget.js +1 -1
  16. package/dist/extensions/mega-turn-store.js +5 -0
  17. package/dist/src/bridge/factory.js +195 -0
  18. package/dist/src/bridge/types.js +10 -0
  19. package/dist/src/bridge.js +1 -0
  20. package/dist/src/contextHealth.js +13 -5
  21. package/dist/src/metrics/turns.js +6 -1
  22. package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
  23. package/dist/src/store/sqlite/context-health.js +6 -48
  24. package/dist/src/store/sqlite/embedding-cache.js +4 -0
  25. package/dist/src/store/sqlite/schema.js +4 -0
  26. package/dist/src/store/turns/memory-store/admin.js +119 -0
  27. package/dist/src/store/turns/memory-store/ctx.js +28 -0
  28. package/dist/src/store/turns/memory-store/read.js +110 -0
  29. package/dist/src/store/turns/memory-store/write.js +88 -0
  30. package/dist/src/store/turns/memory-store.js +44 -273
  31. package/dist/src/store/turns/migrations.js +4 -0
  32. package/dist/src/store/turns/schema.js +1 -0
  33. package/dist/src/store/turns/sqlite-store/read.js +13 -0
  34. package/dist/src/store/turns/sqlite-store/rows.js +4 -0
  35. package/dist/src/store/turns/sqlite-store/write.js +3 -3
  36. package/dist/src/store/turns/sqlite-store.js +4 -0
  37. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
  50. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
  51. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
  58. package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
  72. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
  88. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
  89. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
  91. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
  92. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
  93. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
  94. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
  95. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
  96. package/extensions/dashboard-client/dist/index.html +1 -1
  97. package/extensions/dashboard-client/src/api/health.ts +1 -0
  98. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
  99. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
  100. package/extensions/mega-compact-child.ts +129 -0
  101. package/extensions/mega-config-types.ts +5 -0
  102. package/extensions/mega-config.ts +5 -0
  103. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
  104. package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
  105. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
  106. package/extensions/mega-events/health-handler.ts +13 -3
  107. package/extensions/mega-events/session-handlers.ts +1 -0
  108. package/extensions/mega-pipeline/compact/run.ts +11 -0
  109. package/extensions/mega-runtime/helpers.ts +46 -0
  110. package/extensions/mega-runtime/internal-errors.ts +30 -0
  111. package/extensions/mega-runtime/runtime-instrumentation.ts +195 -0
  112. package/extensions/mega-runtime/runtime.ts +29 -209
  113. package/extensions/mega-runtime/state.ts +1 -1
  114. package/extensions/mega-runtime/widget.ts +1 -1
  115. package/extensions/mega-turn-store.ts +9 -0
  116. package/package.json +2 -2
  117. package/src/bridge/factory.ts +245 -0
  118. package/src/bridge/types.ts +140 -0
  119. package/src/bridge.ts +24 -0
  120. package/src/contextHealth.ts +26 -6
  121. package/src/metrics/turns.ts +6 -1
  122. package/src/store/sqlite/context-health-cache-poison.ts +97 -0
  123. package/src/store/sqlite/context-health.ts +12 -84
  124. package/src/store/sqlite/embedding-cache.ts +4 -0
  125. package/src/store/sqlite/schema.ts +4 -0
  126. package/src/store/turns/memory-store/admin.ts +171 -0
  127. package/src/store/turns/memory-store/ctx.ts +62 -0
  128. package/src/store/turns/memory-store/read.ts +172 -0
  129. package/src/store/turns/memory-store/write.ts +126 -0
  130. package/src/store/turns/memory-store.ts +51 -330
  131. package/src/store/turns/migrations.ts +4 -0
  132. package/src/store/turns/schema.ts +1 -0
  133. package/src/store/turns/sqlite-store/read.ts +19 -0
  134. package/src/store/turns/sqlite-store/rows.ts +5 -0
  135. package/src/store/turns/sqlite-store/write.ts +3 -2
  136. package/src/store/turns/sqlite-store.ts +5 -0
  137. package/src/store/turns/types.ts +15 -0
  138. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
  139. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
  140. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
  141. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
@@ -13,9 +13,10 @@
13
13
  */
14
14
  import { join } from "node:path";
15
15
  import { VectorStore } from "../../src/vectorStore.js";
16
- import { normalizeSessionId } from "../../src/store.js";
17
16
  import { Logger } from "../../src/log.js";
17
+ export { RING_MAX } from "./internal-errors.js";
18
18
  import { Dashboard } from "../mega-dashboard.js";
19
+ import { createSessionRuntime } from "./helpers.js";
19
20
  import { ensureGameStateWatcherImpl, getCachedGameStateImpl, refreshWidgetGameStateImpl, bumpGameStateImpl, disposeRuntimeImpl, } from "./game-state.js";
20
21
  import { setEffectImpl, armMegaCacheFlareImpl, armAchievementFlareImpl, makeTierCallbackImpl, pushTickerImpl, } from "./effects.js";
21
22
  import { ensurePerfIntervalImpl } from "./perf.js";
@@ -25,11 +26,13 @@ import { snapshotImpl } from "./runtime-snapshot.js";
25
26
  import { pressureImpl, effectiveThresholdImpl, pressureBandImpl, } from "./pressure-getters.js";
26
27
  import { resetRuntimeImpl } from "./reset-runtime.js";
27
28
  import { appendEventImpl } from "./append-event.js";
29
+ import { recordInternalErrorImpl } from "./internal-errors.js";
30
+ import { RuntimeInstrumentation } from "./runtime-instrumentation.js";
28
31
  import { getStateDirImpl } from "./get-state-dir.js";
29
32
  import { renderWidgetImpl } from "./render-widget.js";
30
33
  import { setStatusImpl } from "./status.js";
31
34
  import { engineViewImpl } from "./engine-view.js";
32
- export class MegaRuntime {
35
+ export class MegaRuntime extends RuntimeInstrumentation {
33
36
  config;
34
37
  // Store/dashboard/logger are rebound per-repo by bindRepo() so each git repo
35
38
  // gets its own isolated state dir. They start bound to the global default.
@@ -39,197 +42,13 @@ export class MegaRuntime {
39
42
  activeRepoRoot = null;
40
43
  currentStateDir;
41
44
  // The only mutable per-session state. Reset on session_start / session_tree.
42
- rt = {
43
- sessionId: normalizeSessionId(undefined),
44
- persistedThisSession: false,
45
- lastCheckpointId: undefined,
46
- lastCompactedFrom: 0,
47
- lastCompactedTokens: 0,
48
- dedupSkips: 0,
49
- dedupAttempts: 0,
50
- tokensSaved: 0,
51
- lastCompactAt: null,
52
- lastRecallAt: null,
53
- lastInjectAt: null,
54
- _prevCacheHitPct: null,
55
- _lastCacheHealthScore: undefined,
56
- lastNativeCompactAt: null,
57
- compactCount: 0,
58
- recallInjections: 0,
59
- cacheHitTokens: 0,
60
- lengthStopPending: false,
61
- errorRetryCount: 0,
62
- errorRetryUntil: 0,
63
- consecutiveErrors: 0,
64
- lastErrorRetryAt: 0,
65
- retryNudgePending: false,
66
- errorRetrySessionCount: 0,
67
- lastErrorText: undefined,
68
- errorTextRepeatCount: 0,
69
- poisonedAdviseSent: false,
70
- providerOutageAdvised: false,
71
- poisonedCompactSignatures: new Set(),
72
- // S53: tracks whether withRecallTail injected this turn (consumed at turn_end).
73
- recallInjectedThisTurn: false,
74
- poisonedCount: 0,
75
- extensionInitiatedTurn: false,
76
- };
77
- // v0.8.6 cache-stability: the cached live-trim view for the current
78
- // compaction epoch. Set after a fresh runCompact + computeLiveTrimCut, and
79
- // replayed verbatim on subsequent gated context events in the SAME epoch
80
- // (same checkpointId) so the provider KV-cache prefix stays stable instead
81
- // of being invalidated by a freshly regenerated summary + sentinel every
82
- // fire. Invalidated on session restart (resetRuntime) and on any native
83
- // durable compaction (session_compact) that truncates the transcript.
84
- trimCache = null;
85
- debounceUntil = 0;
86
- // S16: debounce for the agent_end resume nudge (avoid busy-loops).
87
- resumeNudgeUntil = 0;
88
- // Agent tracking for real-time widget updates
89
- activeAgents = 0;
90
- currentTurn = 0;
91
- // S33: transient MEGA CACHE flare flag (armed by the turn_end scoring hook
92
- // when cachePct > 100). Copied into widgetData.megaCacheFlare on the next
93
- // snapshot() so the widget renders the oopsie gag, then reset (one cycle).
94
- megaCacheFlare = false;
95
- /** v0.8.3: ambient effect state for animated panel borders keyed off
96
- * status transitions (level-up, mega-cache overshoot, achievement unlock,
97
- * compaction start). Threaded into widgetData as `activeEffect`; the widget
98
- * computes the per-frame phase from startedAt vs Date.now() (non-expired).
99
- * Null when idle/expired. */
100
- activeEffect = null;
101
- megaCacheFlarePct = 0;
102
- levelUpFlare = false;
103
- lastLevel = 0;
104
- // S35: transient achievement-unlock flare (armed by the scoring hooks after
105
- // evaluateAndUnlockAchievements returns newly-unlocked titles). Copied into
106
- // widgetData.achievementFlare on the next snapshot() so the widget renders the
107
- // unlock toast, then reset (one cycle — mirrors megaCacheFlare/levelUpFlare).
108
- achievementFlare = false;
109
- achievementFlareTitles = [];
110
- // S33: last cumulative dedup-collapsed count seen by the session_compact
111
- // hook, so we only record the DELTA as the dedupe score (leaderboard sums).
112
- lastDedupCollapsed = 0;
113
- // Recall block produced by auto-inline (resume/branch) that the next
114
- // before_agent_start should prepend to the system prompt. Unset after use.
115
- pendingRecallBlock;
116
- // S21: memory recall block, parallel to pendingRecallBlock. Same one-shot
117
- // semantics; composed with the checkpoint block in before_agent_start.
118
- pendingMemoryRecallBlock;
119
- statusKey; // current status text for dashboard
120
- // Active model/provider (for real cost estimation). Captured from ctx.model
121
- // on model_select + session_start; persisted to SQL so cost + the dashboard
122
- // can read it without a live ctx.
123
- currentModel;
124
- // Live "what it's doing right now" timestamp, used for the fresh-window.
125
- lastActivityAt = 0;
126
- // Live per-tier dedup trace (Phase 1): e.g. "L0 ✓ → L1 ✓ → L2 0.91 → stored".
127
- // Built from the store's sync onTier callback during a compaction so the user
128
- // watches each tier evaluate in real time. Cleared once the outcome settles.
129
- tierTrace;
130
- // Phase 3 — standout toolbar state.
131
- // Recall/activity ticker: a small ring buffer (≤5) of recent compact/recall
132
- // events so the widget shows a live history instead of a single last action.
133
- ticker = [];
134
- TICKER_MAX = 5;
135
- // Pulsing status: set true while a compaction is in flight, cleared on result.
136
- pulsing = false;
137
- // S21.2: set by `applyMemoryOps` when a memory add/replace/remove lands in
138
- // the current compaction. The pipeline reads this after a successful compact
139
- // to decide whether to fire `consolidateMemories` (skip the work entirely
140
- // when no memory rows changed).
141
- memoriesTouchedThisCompaction = 0;
142
- // Rolling "saved" goal for the progress bar — grows as we save more, so the
143
- // bar always has a meaningful denominator (never sits at 100% forever).
144
- savedGoal = 50_000;
145
- // Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
146
- // while fresh.
147
- lastWhy = undefined;
148
- // v0.8.8 Perf dashboard instrumentation: turn/provider start timestamps +
149
- // the 5s cpu/mem interval handle (one per MegaRuntime, cleared in dispose()).
150
- perfTurnStart = 0;
151
- perfProviderStart = 0;
152
- perfCpuInterval = null;
153
- perfCpuBaseline;
154
- // Context tracking for the dashboard (updated in the context handler).
155
- lastCtxTokens = null;
156
- lastCtxPercent = null;
157
- lastCtxWindow = 0;
158
- // Latest computed widget payload (recomputed per snapshot, rendered per frame).
159
- widgetData = null;
160
- // v0.8.5: material-change signature from the last full snapshot() body. When
161
- // the next snapshot()'s signature matches, the expensive recompute (6 sync
162
- // SQLite opens) + writeFileSync(dashboard.json) are skipped — only the
163
- // (already-registered) widget factory is refreshed. Kills the per-event
164
- // main-thread block during typing/idle streaming with no material change.
165
- lastSnapshotSig = null;
166
- lastHeartbeatAt = 0;
167
- // v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
168
- // for in-process /mega-game writes, the fs.watch callback for cross-process
169
- // dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
170
- // invalidates and the widget re-reads theme/mode after the change.
171
- gameStateBump = 0;
172
- // Cached cross-repo drift status (recomputed at most every 30s — it opens the
173
- // machine-wide registry DB, so we don't want to do it on every render frame).
174
- driftCache = null;
175
- // S31: cached game-mode state (game_mode_on/theme/tui_display_mode). Lazily
176
- // read from the game_state SQLite row on the first widget render, then
177
- // memoized until bumpGameState() evicts it (called by /mega-game after a
178
- // write) so the widget picks up theme/mode/level changes live without
179
- // re-querying the DB on every render frame.
180
- cachedGameState;
181
- // S32: fs.watch on the current repo's sqlite.db so cross-process writes
182
- // (e.g. the dashboard server's PUT /api/game-state, which runs as a detached
183
- // child with no MegaRuntime ref) evict the cached game-state memo. Without
184
- // this, /mega-game's in-process bumpGameState() is the only eviction trigger
185
- // and the widget would keep showing stale theme/mode/toggle after a dashboard
186
- // edit until a restart. The watcher tracks currentStateDir — closed + re-opened
187
- // by ensureGameStateWatcher() on every bindRepo repo switch. Non-fatal: any
188
- // fs.watch failure (missing file / platform issue) is swallowed; the next
189
- // getCachedGameState() snapshot re-queries the DB anyway.
190
- gameStateWatcher;
191
- gameStateWatchDir;
192
- // P2: the last ExtensionContext handed to snapshot()/renderWidget(), stashed
193
- // so the fs.watch game-state callback can force a widget re-render without
194
- // a context event (cross-process dashboard edits while pi is idle). Cleared
195
- // implicitly on construction (undefined → watcher skips until first snap).
196
- lastWidgetCtx;
197
- /**
198
- * DIAG counters for the "team run doesn't relieve context" investigation.
199
- * Plain integers, incremented at the three compaction decision points. They
200
- * let a headless test drive the real event handlers and assert the firing
201
- * cadence without scraping log files. Inert in production (the live-trim and
202
- * before-compact probes also emit logger.info, but these counters are always
203
- * updated and cost nothing).
204
- */
205
- diagLiveTrimFires = 0; // context handler returned a trimmed view
206
- diagLiveTrimReplays = 0; // v0.8.6: trim view returned via cached replay (skipped re-compact)
207
- diagBeforeCompactFires = 0; // session_before_compact handler entered
208
- diagBeforeCompactSupplied = 0; // session_before_compact supplied our trim
209
- diagAgentEndIdle = 0; // agent_end with activeAgents===0
210
- diagAgentEndDurable = 0; // agent_end fired ctx.compact() (mid-run durable trim)
211
- diagAgentEndDurableSkipRecent = 0; // agent_end skipped ctx.compact() — compaction in last 10s (race guard)
212
- // Per-skip-path counters for the team-run diagnosis.
213
- diagCtxFastGate = 0; // returned at token fast-gate (below threshold)
214
- diagCtxNoCompact = 0; // autoCompactCheck().shouldCompact === false
215
- diagCtxDebounce = 0; // debounceUntil not yet elapsed
216
- diagCtxRunSkipped = 0; // runCompact() returned skipped
217
- diagCtxCutNull = 0; // computeLiveTrimCut returned null (anchor/boundary)
218
- diagCtxThrown = 0; // live-trim try threw (caught)
219
- // Context health instrumentation (v0.12): rolling ring buffers for
220
- // drift detection + cache poison Layer 1 hash baseline.
221
- recentTurnEmbeddings = [];
222
- recentErrorCategories = [];
223
- lastPrefixHash = null;
224
- lastErrorCategory = null;
225
- /**
226
- * S26 capture instrumentation: the "model_snapshots empty → $0.00 cost card"
227
- * bug was invisible because captureModel swallowed the DB write in a silent
228
- * `catch {}`. These always-updated counters (zero cost) let a headless test or
229
- * a live capture tell whether captureModel ran and whether the snapshot landed.
230
- */
231
- diagCaptureModelCalls = 0; // captureModel entered with a populated ctx.model
232
- diagCaptureModelFails = 0; // recordModelSnapshot threw → model_snapshots stays empty
45
+ // Initialized via createSessionRuntime() (helpers.ts delegate-shell split).
46
+ rt = createSessionRuntime();
47
+ // All other field declarations (instrumentation counters, context-health
48
+ // rings, the Sprint-H internal-error ring, live display / cache-stability
49
+ // state) live on the RuntimeInstrumentation base class — see
50
+ // runtime-instrumentation.ts. Field names are unchanged (this.diagXxx,
51
+ // this.trimCache, this.recentInternalErrors, etc).
233
52
  // ---- pressure accessors (bodies in pressure-getters.ts) -------------------
234
53
  /** Live 0–1 pressure — see `pressureImpl` in pressure-getters.ts for the
235
54
  * dual-basis (percent vs token) reconciliation notes. Thin delegate. */
@@ -247,6 +66,7 @@ export class MegaRuntime {
247
66
  return pressureBandImpl(this);
248
67
  }
249
68
  constructor(config) {
69
+ super();
250
70
  this.config = config;
251
71
  this.store = new VectorStore({
252
72
  dedupSim: config.dedupSim,
@@ -299,6 +119,20 @@ export class MegaRuntime {
299
119
  appendEvent(event, fields) {
300
120
  appendEventImpl(this, event, fields);
301
121
  }
122
+ /**
123
+ * Sprint H (Finding 3 / Option A): record an internal store/service-write
124
+ * failure category into the `recentInternalErrors` ring. Called AT each
125
+ * failure emit site (see the §2.3a audit in
126
+ * docs/specs/c2-resume-and-health-fixes.md) — never by a central log filter.
127
+ * Mirrors `recentErrorCategories` (cap = RING_MAX, shift when over).
128
+ *
129
+ * NOTE (process boundary): the ring is per-runtime / in-memory. A child
130
+ * subprocess's failures do NOT reach the parent's ring; the parent-side
131
+ * dashboard already aggregates events.log cross-process, so nothing is lost.
132
+ */
133
+ recordInternalError(category) {
134
+ recordInternalErrorImpl(this, category);
135
+ }
302
136
  /** S21: state dir of the currently bound repo (where memories live) — thin
303
137
  * delegate to `getStateDirImpl` (get-state-dir.ts). */
304
138
  getStateDir() {
@@ -5,4 +5,4 @@
5
5
  * every existing `import { MegaRuntime } from "./state.js"` continues to
6
6
  * resolve without changes.
7
7
  */
8
- export { MegaRuntime } from "./runtime.js";
8
+ export { MegaRuntime, RING_MAX } from "./runtime.js";
@@ -85,7 +85,7 @@ export function buildWidgetLines(wd, width, activeAgents) {
85
85
  `${C.gray}all-time${C.reset} ${fmtTokens(wd.repoIn)}→${fmtTokens(wd.repoKept)} kept ${C.blue}(${wd.rTxt}% freed)${C.reset}`,
86
86
  `${wd.repoChk} chk/${wd.repoSess} sess`,
87
87
  `${C.gray}mem${C.reset} ${wd.embedderName} · ${wd.chk} chunks · ${C.blue}comp ${wd.compStr}${C.reset}`,
88
- `${C.gray}drift${C.reset} ${wd.driftStatus === "ok" ? C.green : C.amber}${wd.driftStatus}${C.reset}`,
88
+ `${C.gray}comp lag${C.reset} ${wd.driftStatus === "ok" ? C.green : C.amber}${wd.driftStatus}${C.reset}`,
89
89
  `${C.gray}compact${C.reset} ${sinceCompactStr(wd.sinceCompact)}`,
90
90
  ...(wd.perTurnCacheHitPct != null
91
91
  ? [
@@ -24,6 +24,10 @@ function storeFor(stateDir) {
24
24
  }
25
25
  return s;
26
26
  }
27
+ /** Read-only view of the turn store for a state dir (e.g. nextTurnIndexFor). */
28
+ export function turnReaderFor(stateDir) {
29
+ return storeFor(stateDir).asReader();
30
+ }
27
31
  /** Map a legacy RecallSource onto the contract `TurnRecallEntry.source` enum. */
28
32
  function mapSource(s) {
29
33
  switch (s) {
@@ -52,6 +56,7 @@ export function recordTurnWrite(config, input, stateDir) {
52
56
  conversationId: input.conversationId,
53
57
  sessionId: input.sessionId,
54
58
  turnIndex: input.turnIndex,
59
+ sessionTurnIndex: input.sessionTurnIndex,
55
60
  role: input.role,
56
61
  endedAt: input.endedAt ?? input.startedAt ?? Date.now(),
57
62
  ctxTokens: input.ctxTokens,
@@ -0,0 +1,195 @@
1
+ /**
2
+ * bridge/factory.ts — `createMegaBridge(opts)` implementation.
3
+ *
4
+ * A thin, pi-agnostic wrapper over the engine's compaction / recall / memory /
5
+ * fork / vector APIs. Stores are constructed lazily on first use (a consumer
6
+ * that only calls recallMemories pays no VectorStore cost). Exceptions
7
+ * propagate from every method except `fork` (catches ForkError by design) and
8
+ * `close` (swallows best-effort cleanup), so failures surface in tests.
9
+ */
10
+ import { compactSession } from "../engine.js";
11
+ import { recallAndInline, recallAndInlineAsync, recallMemoriesAndInline, } from "../recall.js";
12
+ import { forkFromConversation, ForkError } from "../fork.js";
13
+ import { createTurnStore } from "../store/turns/index.js";
14
+ import { addMemory } from "../store/sqlite/memories.js";
15
+ import { VectorStore, vectorSearch } from "../vectorStore.js";
16
+ import { repoKey } from "../store/repoKey.js";
17
+ /** Map a RecallInjectResult to the bridge's slimmer result contract. */
18
+ function mapRecallResult(r) {
19
+ return {
20
+ block: r.block,
21
+ report: r.report,
22
+ hitCount: r.toInject.length,
23
+ empty: r.empty,
24
+ };
25
+ }
26
+ /** Map the memoryRecallAndInline tuple result to the bridge contract. */
27
+ function mapMemoryResult(r) {
28
+ return {
29
+ block: r.block,
30
+ report: r.report,
31
+ hitCount: r.report.length,
32
+ empty: r.empty,
33
+ };
34
+ }
35
+ /** Map vectorSearch hits to the cortex result contract. */
36
+ function mapCortexHits(hits, limit) {
37
+ const top = hits.slice(0, limit);
38
+ return {
39
+ results: top.map((h) => ({
40
+ checkpointId: h.checkpoint.checkpointId,
41
+ score: h.score,
42
+ summary: h.checkpoint.summary,
43
+ })),
44
+ hitCount: top.length,
45
+ };
46
+ }
47
+ /**
48
+ * Create a MegaBridge over a single stateDir.
49
+ *
50
+ * The VectorStore and TurnStore are lazy: constructed on first use and cached
51
+ * in closures. The stateDir is retained for memory recall, which needs it
52
+ * directly.
53
+ */
54
+ export function createMegaBridge(opts) {
55
+ const stateDir = opts.stateDir;
56
+ let vectorStore;
57
+ let turnStore;
58
+ const getVectorStore = () => {
59
+ if (!vectorStore)
60
+ vectorStore = new VectorStore({ stateDir });
61
+ return vectorStore;
62
+ };
63
+ const getTurnStore = () => {
64
+ if (!turnStore)
65
+ turnStore = createTurnStore({ stateDir });
66
+ return turnStore;
67
+ };
68
+ return {
69
+ compact(input) {
70
+ const result = compactSession({
71
+ sessionId: input.sessionId,
72
+ messages: input.messages,
73
+ keepFrom: input.keepFrom,
74
+ summary: input.summary,
75
+ keyDecisions: input.keyDecisions,
76
+ nextSteps: input.nextSteps,
77
+ filesModified: input.filesModified,
78
+ compressionPressure: input.compressionPressure,
79
+ }, getVectorStore());
80
+ return {
81
+ skipped: result.skipped,
82
+ deduped: result.deduped,
83
+ summary: result.summary,
84
+ checkpointId: result.checkpointId,
85
+ tokenEstimate: result.tokenEstimate,
86
+ originalTokenEstimate: result.originalTokenEstimate,
87
+ compactedFrom: result.compactedFrom,
88
+ };
89
+ },
90
+ recallCheckpoints(opts) {
91
+ const recallOpts = {
92
+ sessionId: opts.sessionId,
93
+ query: opts.query,
94
+ limit: opts.limit ?? 3,
95
+ source: "command",
96
+ skipInjected: opts.skipInjected,
97
+ recallMaxTokens: opts.recallMaxTokens,
98
+ };
99
+ return mapRecallResult(recallAndInline(recallOpts, getVectorStore()));
100
+ },
101
+ async recallMemories(opts) {
102
+ const memOpts = {
103
+ query: opts.query,
104
+ stateDir,
105
+ limit: opts.limit,
106
+ minSimilarity: opts.minSimilarity,
107
+ crossRepo: opts.crossRepo,
108
+ crossRepoCosine: opts.crossRepoCosine,
109
+ recallMaxTokens: opts.recallMaxTokens,
110
+ };
111
+ const r = await recallMemoriesAndInline(memOpts);
112
+ return mapMemoryResult(r);
113
+ },
114
+ async recallAndInlineAsync(opts) {
115
+ const recallOpts = {
116
+ sessionId: opts.sessionId,
117
+ query: opts.query,
118
+ limit: opts.limit ?? 3,
119
+ source: "command",
120
+ skipInjected: opts.skipInjected,
121
+ recallMaxTokens: opts.recallMaxTokens,
122
+ };
123
+ const r = await recallAndInlineAsync(recallOpts, getVectorStore());
124
+ return mapRecallResult(r);
125
+ },
126
+ fork(opts) {
127
+ try {
128
+ const outcome = forkFromConversation(getTurnStore(), opts.parentConversationId, opts.turnIndex);
129
+ return {
130
+ childConversationId: outcome.childConversationId,
131
+ checkpointIds: outcome.checkpointIds,
132
+ forkTurnIndex: opts.turnIndex,
133
+ };
134
+ }
135
+ catch (e) {
136
+ if (e instanceof ForkError) {
137
+ return { error: e.code };
138
+ }
139
+ // The bridge is pi-agnostic (no runtime handle) — the host-side adapter
140
+ // (ithacus) owns the internal-error ring for fork failures, so this
141
+ // re-thrown non-ForkError is recorded there, not here.
142
+ // guardrails-allow INTERNAL-ERR: bridge is pi-agnostic; no runtime handle — host adapter records fork failures
143
+ throw e;
144
+ }
145
+ },
146
+ cortexQuery(opts) {
147
+ const limit = opts.limit ?? 3;
148
+ const scope = opts.repo ?? repoKey(stateDir);
149
+ const hits = vectorSearch(getVectorStore(), scope, opts.query, limit);
150
+ return mapCortexHits(hits, limit);
151
+ },
152
+ addMemory(input) {
153
+ // repo === null ⇒ stateDir-scoped durable memory (matches recallMemories).
154
+ return addMemory({
155
+ kind: input.kind,
156
+ content: input.content,
157
+ tags: input.tags,
158
+ category: input.category,
159
+ }, null, stateDir);
160
+ },
161
+ recordTurn(input) {
162
+ // S49R: child turns get the conversation-monotonic index (MAX+1) so a
163
+ // re-dispatched child (same conversation, restarted session counter) never
164
+ // collides with UNIQUE(conversation_id, turn_index). Carry the child's
165
+ // session counter as sessionTurnIndex for the raw_transcript join.
166
+ const turnIndex = getTurnStore().asReader().nextTurnIndexFor(input.conversationId);
167
+ const turn = {
168
+ conversationId: input.conversationId,
169
+ sessionId: input.sessionId,
170
+ turnIndex,
171
+ sessionTurnIndex: input.turnIndex,
172
+ role: input.role ?? "assistant",
173
+ endedAt: input.endedAt ?? Date.now(),
174
+ ctxTokens: input.ctxTokens,
175
+ ctxPercent: input.ctxPercent,
176
+ model: input.model,
177
+ };
178
+ // The bridge is pi-agnostic (no runtime handle); the host-side adapter
179
+ // (ithacus) owns the internal-error ring for appendTurn failures. The
180
+ // recordTurn child-path twin of Finding 2 is recorded there, not here.
181
+ // guardrails-allow INTERNAL-ERR: bridge is pi-agnostic; no runtime handle — host adapter records appendTurn failures
182
+ getTurnStore().asWriter().appendTurn(turn);
183
+ },
184
+ close() {
185
+ if (turnStore) {
186
+ try {
187
+ turnStore.close();
188
+ }
189
+ catch {
190
+ /* best-effort */
191
+ }
192
+ }
193
+ },
194
+ };
195
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * bridge/types.ts — contract types for the bidirectional mega-compact bridge.
3
+ *
4
+ * A pi-agnostic, unit-testable adapter surface that wraps the engine's
5
+ * compaction / recall / memory / fork / vector APIs behind one factory so an
6
+ * external host (ithacus) can drive them without importing pi-runtime types.
7
+ * Every type here mirrors a real engine signature (see factory.ts for the
8
+ * wiring).
9
+ */
10
+ export {};
@@ -0,0 +1 @@
1
+ export { createMegaBridge } from "./bridge/factory.js";
@@ -15,14 +15,21 @@
15
15
  */
16
16
  import { openStore } from "./store/sqlite/utils.js";
17
17
  /**
18
- * Weighted composite health score from five sub-dimensions.
18
+ * Weighted composite health score from six sub-dimensions (Sprint H — B2).
19
19
  *
20
20
  * Weights:
21
21
  * output quality 22% — primary measure of model output health
22
22
  * drift 22% — session-level coherence
23
23
  * cache poison 20% — prompt-cache integrity
24
24
  * cache health 18% — cache hit rate stability
25
- * error rate 18% — error frequency
25
+ * error rate 9% — API-retry error frequency (split from 18%)
26
+ * store error 9% — internal store-write failure frequency (NEW 6th axis)
27
+ *
28
+ * The 0.18 `errorRate` budget was split into `errorRate 0.09` +
29
+ * `storeErrorRate 0.09` so a silent store-write failure is as visible as a
30
+ * retryable API error. Weights sum to 1.0. NOTE (step-change): historical
31
+ * context_health rows keep their stored composite (point-in-time snapshots — no
32
+ * backfill), so the trend line shows a boundary step where weights change.
26
33
  *
27
34
  * Returns 0–1: 1 = fully healthy, 0 = severely degraded.
28
35
  */
@@ -31,7 +38,8 @@ export function computeHealthScore(sub) {
31
38
  sub.drift * 0.22 +
32
39
  sub.cachePoison * 0.20 +
33
40
  sub.cacheHealth * 0.18 +
34
- sub.errorRate * 0.18;
41
+ sub.errorRate * 0.09 +
42
+ sub.storeErrorRate * 0.09;
35
43
  return Math.max(0, Math.min(1, raw));
36
44
  }
37
45
  /**
@@ -47,9 +55,9 @@ export function recordContextHealth(stateDir, row) {
47
55
  const db = openStore(stateDir);
48
56
  db.prepare(`INSERT INTO context_health
49
57
  (ts, session_id, turn_index, drift_score, output_quality,
50
- error_score, cache_health, cache_poison, composite,
58
+ error_score, cache_health, cache_poison, composite, store_error_score,
51
59
  model_id, repetition_ratio, coherence_score, prefix_hash)
52
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.ts, row.sessionId, row.turnIndex, row.driftScore, row.outputQuality, row.errorScore, row.cacheHealth, row.cachePoison, row.composite, row.modelId ?? null, row.repetitionRatio ?? null, row.coherenceScore ?? null, row.prefixHash ?? null);
60
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.ts, row.sessionId, row.turnIndex, row.driftScore, row.outputQuality, row.errorScore, row.cacheHealth, row.cachePoison, row.composite, row.storeErrorScore ?? null, row.modelId ?? null, row.repetitionRatio ?? null, row.coherenceScore ?? null, row.prefixHash ?? null);
53
61
  }
54
62
  catch {
55
63
  /* non-fatal: context health instrumentation never blocks the agent loop */
@@ -50,7 +50,12 @@ export function turnMetrics(store, mainDb, conversationId) {
50
50
  }
51
51
  return turns.map((t) => {
52
52
  const recall = store.listRecallByIndex(conversationId, t.turnIndex);
53
- const agg = rawAggBySessionTurn.get(`${t.sessionId}::${t.turnIndex}`);
53
+ // S49R: raw_transcript is keyed by pi's per-session counter. Pre-migration
54
+ // turn rows have NULL sessionTurnIndex and their turnIndex IS the session
55
+ // counter, so fall back to turnIndex (COALESCE). Post-migration rows join
56
+ // on sessionTurnIndex so resume doesn't zero the raw message count.
57
+ const sessionKey = t.sessionTurnIndex ?? t.turnIndex;
58
+ const agg = rawAggBySessionTurn.get(`${t.sessionId}::${sessionKey}`);
54
59
  const rawCount = agg?.raw_count ?? 0;
55
60
  const uniqueCount = agg?.unique_count ?? 0;
56
61
  return {
@@ -0,0 +1,60 @@
1
+ /**
2
+ * context-health-cache-poison.ts — `cache_poison_events` table accessors.
3
+ *
4
+ * Extracted from context-health.ts (Sprint H split — context-health.ts crossed
5
+ * the soft limit when storeErrorScore was added). Records cache-poison advisory
6
+ * events when cache corruption/inconsistency is detected at a given cache layer.
7
+ *
8
+ * PREVENT-PI-004: local SQLite only, zero network.
9
+ * PREVENT-002: all SQL uses ? placeholders, no string-concatenated values.
10
+ * Pi-agnostic: no pi runtime types.
11
+ */
12
+ import { getStateDir } from "../../store.js";
13
+ import { openStore } from "./utils.js";
14
+ /**
15
+ * Record one cache-poison advisory event. All values are fully parameterized
16
+ * (PREVENT-002). Non-fatal: any write error is logged to stderr and silently
17
+ * swallowed so advisory logging never blocks the agent loop.
18
+ */
19
+ export function recordCachePoisonEvent(stateDir = getStateDir(), event) {
20
+ try {
21
+ const db = openStore(stateDir);
22
+ db.prepare(`INSERT INTO cache_poison_events
23
+ (ts, turn_index, session_id, layer, detail, severity)
24
+ VALUES (?, ?, ?, ?, ?, ?)`).run(event.ts, event.turnIndex, event.sessionId, event.layer, event.detail, event.severity);
25
+ }
26
+ catch (err) {
27
+ // Non-fatal: advisory logging must never block the agent loop.
28
+ const msg = err instanceof Error ? err.message : String(err);
29
+ process.stderr.write(`[context-health] recordCachePoisonEvent error: ${msg}\n`);
30
+ }
31
+ }
32
+ /**
33
+ * Read cache poison events since `sinceTs`, ordered descending by ts (most
34
+ * recent first), capped at 100 rows. Returns an empty array on any error
35
+ * (non-fatal). SQL is fully parameterized (PREVENT-002).
36
+ */
37
+ export function readCachePoisonEvents(stateDir = getStateDir(), sinceTs) {
38
+ try {
39
+ const db = openStore(stateDir);
40
+ const rows = db
41
+ .prepare(`SELECT id, ts, turn_index, session_id, layer, detail, severity
42
+ FROM cache_poison_events
43
+ WHERE ts >= ?
44
+ ORDER BY ts DESC
45
+ LIMIT 100`)
46
+ .all(sinceTs);
47
+ return rows.map((r) => ({
48
+ id: r.id,
49
+ ts: r.ts,
50
+ turnIndex: r.turn_index,
51
+ sessionId: r.session_id,
52
+ layer: r.layer,
53
+ detail: r.detail,
54
+ severity: r.severity,
55
+ }));
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }