pi-mega-compact 0.20.22 → 0.20.24

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 (104) hide show
  1. package/dist/extensions/dashboard-server/resolve-snapshot.js +42 -28
  2. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
  3. package/dist/extensions/dashboard-server/routes-setup.js +4 -1
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-crystals.js +11 -3
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-diagnostics.js +7 -2
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-economics.js +1 -0
  7. package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +18 -6
  8. package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +1 -0
  9. package/dist/extensions/dashboard-server/routes-vector-cortex-reconstruct.js +10 -3
  10. package/dist/extensions/dashboard-server/routes-vector-cortex-repair.js +13 -5
  11. package/dist/extensions/dashboard-server/routes-vector-cortex-shards.js +12 -4
  12. package/dist/extensions/dashboard-server/vc-event-counts.js +63 -0
  13. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +15 -4
  14. package/dist/extensions/mega-events/context-handler/afterCompact.js +57 -4
  15. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +35 -7
  16. package/dist/extensions/mega-events/context-handler/pipelineRun.js +33 -0
  17. package/dist/extensions/mega-events/context-handler/tailResult.js +45 -12
  18. package/dist/extensions/mega-runtime/dashboard-snapshot.js +9 -6
  19. package/dist/extensions/mega-runtime/runtime-snapshot.js +2 -0
  20. package/dist/extensions/mega-runtime/vc-observer.js +98 -0
  21. package/dist/src/cache-stripe-impl.js +52 -214
  22. package/dist/src/cache-stripe-score.js +115 -0
  23. package/extensions/dashboard-client/dist/assets/{AreaChart-BkfB-LSQ.js → AreaChart-BzEedilo.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{AreaChart-BkfB-LSQ.js.map → AreaChart-BzEedilo.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{BarChart-Cs1kogko.js → BarChart-CzAVg1_J.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{BarChart-Cs1kogko.js.map → BarChart-CzAVg1_J.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{CacheTab-BZgaguGr.js → CacheTab-D2-rzTuX.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{CacheTab-BZgaguGr.js.map → CacheTab-D2-rzTuX.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{EventsTab-BFbkI_MO.js → EventsTab-DB7g1Szy.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{EventsTab-BFbkI_MO.js.map → EventsTab-DB7g1Szy.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{HealthTab-CvlOENCX.js → HealthTab-Cvkmi-R3.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{HealthTab-CvlOENCX.js.map → HealthTab-Cvkmi-R3.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CtBuz2qL.js → MaintenanceTab-g_udcOO7.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CtBuz2qL.js.map → MaintenanceTab-g_udcOO7.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-K4775S4d.js → MemoryMapTab-DAjBaf8P.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-K4775S4d.js.map → MemoryMapTab-DAjBaf8P.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{MetricsTab-DVGcFInz.js → MetricsTab-13dYYocB.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{MetricsTab-DVGcFInz.js.map → MetricsTab-13dYYocB.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{OverviewTab-4LmW5ZCl.js → OverviewTab-CLpCyLvx.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{OverviewTab-4LmW5ZCl.js.map → OverviewTab-CLpCyLvx.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{ReposTab-StebchgQ.js → ReposTab-EdAqUrwG.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{ReposTab-StebchgQ.js.map → ReposTab-EdAqUrwG.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SessionsTab-C3D_XAkX.js → SessionsTab-C21ZuOL_.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SessionsTab-C3D_XAkX.js.map → SessionsTab-C21ZuOL_.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/SetupTab-CP7lE3L_.js +2 -0
  46. package/extensions/dashboard-client/dist/assets/SetupTab-CP7lE3L_.js.map +1 -0
  47. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CTPi_AAW.js → TimeSavedCard-DZ3QR8qt.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CTPi_AAW.js.map → TimeSavedCard-DZ3QR8qt.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{TurnsTab-DSOGcY5Q.js → TurnsTab-CNchE5rD.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-DSOGcY5Q.js.map → TurnsTab-CNchE5rD.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-OMW8_RFf.js → VectorCortexTab-B3WuPeyn.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-OMW8_RFf.js.map → VectorCortexTab-B3WuPeyn.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{WikiTab-Db-qFDdg.js → WikiTab-BIe5Z9Nc.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{WikiTab-Db-qFDdg.js.map → WikiTab-BIe5Z9Nc.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{button-BF4UgBAD.js → button-BrVreSt5.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{button-BF4UgBAD.js.map → button-BrVreSt5.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{card-XmXoYQqB.js → card-D9F4wbqs.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{card-XmXoYQqB.js.map → card-D9F4wbqs.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Be8hM1aU.js → generateCategoricalChart-mkdzCnMP.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Be8hM1aU.js.map → generateCategoricalChart-mkdzCnMP.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{index-NXDgILxv.js → index-CtlmOlj1.js} +3 -3
  62. package/extensions/dashboard-client/dist/assets/{index-NXDgILxv.js.map → index-CtlmOlj1.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{switch-kUZCrbia.js → switch-BXmp5iwA.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{switch-kUZCrbia.js.map → switch-BXmp5iwA.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{toggle-DEe5mCwF.js → toggle-BgawNwW9.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{toggle-DEe5mCwF.js.map → toggle-BgawNwW9.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{useSSE-BlCyLqge.js → useSSE-DZKX73Ea.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{useSSE-BlCyLqge.js.map → useSSE-DZKX73Ea.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/index.html +1 -1
  70. package/extensions/dashboard-client/src/tabs/SetupTab/CustomEndpointSection.tsx +65 -0
  71. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +19 -108
  72. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetupStyles.ts +69 -0
  73. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +5 -3
  74. package/extensions/dashboard-server/api-contracts/setup.ts +4 -3
  75. package/extensions/dashboard-server/api-contracts/vector-cortex-cache.ts +5 -0
  76. package/extensions/dashboard-server/api-contracts/vector-cortex-diagnostics.ts +2 -0
  77. package/extensions/dashboard-server/api-contracts/vector-cortex-economics.ts +2 -0
  78. package/extensions/dashboard-server/api-contracts/vector-cortex-policy.ts +2 -0
  79. package/extensions/dashboard-server/resolve-snapshot.ts +42 -28
  80. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
  81. package/extensions/dashboard-server/routes-setup.ts +3 -1
  82. package/extensions/dashboard-server/routes-vector-cortex-crystals.ts +12 -3
  83. package/extensions/dashboard-server/routes-vector-cortex-diagnostics.ts +8 -2
  84. package/extensions/dashboard-server/routes-vector-cortex-economics.ts +1 -0
  85. package/extensions/dashboard-server/routes-vector-cortex-heal.ts +19 -6
  86. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +1 -0
  87. package/extensions/dashboard-server/routes-vector-cortex-reconstruct.ts +11 -3
  88. package/extensions/dashboard-server/routes-vector-cortex-repair.ts +14 -5
  89. package/extensions/dashboard-server/routes-vector-cortex-shards.ts +13 -4
  90. package/extensions/dashboard-server/vc-event-counts.ts +67 -0
  91. package/extensions/mega-dashboard.ts +2 -0
  92. package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +13 -4
  93. package/extensions/mega-events/context-handler/afterCompact.ts +69 -4
  94. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +39 -7
  95. package/extensions/mega-events/context-handler/pipelineRun.ts +39 -0
  96. package/extensions/mega-events/context-handler/tailResult.ts +56 -15
  97. package/extensions/mega-runtime/dashboard-snapshot.ts +12 -6
  98. package/extensions/mega-runtime/runtime-snapshot.ts +6 -0
  99. package/extensions/mega-runtime/vc-observer.ts +120 -0
  100. package/package.json +1 -1
  101. package/src/cache-stripe-impl.ts +73 -277
  102. package/src/cache-stripe-score.ts +170 -0
  103. package/extensions/dashboard-client/dist/assets/SetupTab-BTBGyppF.js +0 -2
  104. package/extensions/dashboard-client/dist/assets/SetupTab-BTBGyppF.js.map +0 -1
@@ -3,6 +3,21 @@ import { appendMirrorMessages } from "../mirror-append.js";
3
3
  import { appendMessagesToLedger } from "../../mega-runtime/vector-cortex-ledger.js";
4
4
  import { epochIdFor } from "../../../src/mirror/epoch.js";
5
5
  import { messageContentText } from "./messageText.js";
6
+ /**
7
+ * Best-effort tool_call_id for the tool_results insert. The toolResult variant
8
+ * carries a top-level toolCallId (read via an `unknown`-narrowed cast — never
9
+ * reach into `.content`, which is variant-specific and requires narrowing).
10
+ * bashExecution has no toolCallId, so fall back to a stable synthetic id keyed
11
+ * on (turn, index) to satisfy the NOT NULL column. No `any` (PREVENT-011).
12
+ */
13
+ function toolCallIdOf(m, fallback) {
14
+ if (m.role === "toolResult") {
15
+ const id = m.toolCallId;
16
+ if (typeof id === "string" && id.length > 0)
17
+ return id;
18
+ }
19
+ return fallback;
20
+ }
6
21
  /**
7
22
  * Append incoming messages to the DB mirror (raw_transcript + thread/tool
8
23
  * tables) and the v2 ledger. Gated on config.dbMirror for the mirror; the VC1B
@@ -26,22 +41,35 @@ export function appendMirrorAndLedger(runtime, config, messages) {
26
41
  // separation (buildSeparatedPrompt / buildCacheOptimizedPrompt in
27
42
  // tailResult) is sufficient for the prompt-construction path;
28
43
  // these DB writes persist the split for post-hoc analysis, dashboard
29
- // queries, and future readers. Non-fatal failure here never breaks
30
- // the agent loop (PREVENT-PI-004: zero network, local SQLite only).
31
- {
44
+ // queries, and future readers. Gated on (messageSeparation ||
45
+ // cacheStriping) so flag-OFF remains byte-identical to the
46
+ // predecessor — when both flags are OFF the live prompt is never
47
+ // separated, and growing these tables would be dead state.
48
+ // Non-fatal — failure here never breaks the agent loop
49
+ // (PREVENT-PI-004: zero network, local SQLite only).
50
+ if (config.messageSeparation || config.cacheStriping) {
32
51
  const sid = runtime.rt.sessionId;
33
52
  const turn = runtime.currentTurn;
34
53
  const now = Date.now();
35
54
  const threadStmt = db.prepare("INSERT OR IGNORE INTO conversation_thread (conversation_id, role, content, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)");
36
- const toolStmt = db.prepare("INSERT OR IGNORE INTO tool_results (conversation_id, role, content, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)");
37
- for (const m of messages) {
55
+ // Schema (plan-v2.ts) is (conversation_id, tool_call_id,
56
+ // tool_result, turn_index, timestamp) — NOT role/content.
57
+ const toolStmt = db.prepare("INSERT OR IGNORE INTO tool_results (conversation_id, tool_call_id, tool_result, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)");
58
+ const toolHas = db.prepare("SELECT 1 FROM tool_results WHERE conversation_id = ? AND turn_index = ? AND tool_call_id = ? AND tool_result = ? LIMIT 1");
59
+ const threadHas = db.prepare("SELECT 1 FROM conversation_thread WHERE conversation_id = ? AND turn_index = ? AND role = ? AND content = ? LIMIT 1");
60
+ for (const [idx, m] of messages.entries()) {
38
61
  const role = m.role;
39
62
  const content = messageContentText(m);
40
63
  if (role === "user" || role === "assistant") {
41
- threadStmt.run(sid, role, content, turn, now);
64
+ if (threadHas.get(sid, turn, role, content) == null) {
65
+ threadStmt.run(sid, role, content, turn, now);
66
+ }
42
67
  }
43
68
  else if (role === "toolResult" || role === "bashExecution") {
44
- toolStmt.run(sid, role, content, turn, now);
69
+ const toolCallId = toolCallIdOf(m, `bash:${turn}:${idx}`);
70
+ if (toolHas.get(sid, turn, toolCallId, content) == null) {
71
+ toolStmt.run(sid, toolCallId, content, turn, now);
72
+ }
45
73
  }
46
74
  }
47
75
  }
@@ -1,11 +1,39 @@
1
1
  import { runCompact } from "../../mega-pipeline.js";
2
2
  import { pressureFromPct, pressureRatio } from "../../mega-config.js";
3
+ import { recordCompactLatency } from "../../mega-runtime/vc-observer.js";
4
+ import { decideLivePath } from "../../mega-runtime/vector-cortex-live.js";
5
+ import { VC5C_ENABLED } from "../../../src/config/vector-cortex.js";
3
6
  /**
4
7
  * Invoke the compaction pipeline with adaptive pressure. Returns a tailed view
5
8
  * ("return") when compaction skipped, or "proceed" with the non-skipped result
6
9
  * and the computed pressure (consumed by live-trim's critical-over hatch).
7
10
  */
8
11
  export function invokePipeline(pi, runtime, config, ctx, opts) {
12
+ // VC5C: emit the rollout decision per compact event (observability seam).
13
+ // vcGate/vcHardFaults are not yet declared on MegaRuntime.rt — cast to the
14
+ // rollout shapes. Best-effort + non-fatal; decision does NOT gate behavior yet.
15
+ if (VC5C_ENABLED()) {
16
+ try {
17
+ const decision = decideLivePath(runtime.rt.sessionId, {
18
+ emit: runtime.appendEvent.bind(runtime),
19
+ currentGate: runtime.rt.vcGate ?? 0,
20
+ hardFaults: runtime.rt
21
+ .vcHardFaults ?? [],
22
+ });
23
+ runtime.appendEvent("vector_cortex_rollout_decision", {
24
+ sessionId: runtime.rt.sessionId,
25
+ vcActive: decision.vcActive,
26
+ forcedPreVc: decision.forcedPreVc,
27
+ mode: decision.mode,
28
+ bucket: decision.bucket,
29
+ gateIndex: decision.gateIndex,
30
+ promotionBlocked: decision.promotionBlocked,
31
+ });
32
+ }
33
+ catch {
34
+ /* non-fatal: rollout decision emission never breaks compaction */
35
+ }
36
+ }
9
37
  // Adaptive compression (Fix E): scale compression strength + keepFrom depth
10
38
  // with how close we are to the model context limit. Null-safe: when the
11
39
  // token-fallback path ran (pct unavailable) use the token-basis pressure
@@ -13,9 +41,14 @@ export function invokePipeline(pi, runtime, config, ctx, opts) {
13
41
  const pressure = opts.pct != null
14
42
  ? pressureFromPct(opts.pct)
15
43
  : pressureRatio(opts.currentTokens, runtime.effectiveThreshold);
44
+ const t0 = Date.now();
16
45
  const ran = runCompact(pi, runtime, config, ctx, opts.messages, {
17
46
  compressionPressure: pressure,
18
47
  });
48
+ // VC0A: record compact latency on the eval observer (mode A) on every
49
+ // outcome so the dashboard histogram reflects real data. No-op when the
50
+ // observer is absent (flag off / construction failure).
51
+ recordCompactLatency(runtime, Date.now() - t0, runtime.rt.sessionId, runtime.rt.compactCount);
19
52
  // D.3: skip paths fall back to replay instead of returning empty.
20
53
  // If runCompact skipped and we have a valid trimCache, replay it
21
54
  // (free stability win) — otherwise defer to the next event.
@@ -1,6 +1,8 @@
1
1
  import { stagedForTail, withRecallTail } from "../recall-tail.js";
2
2
  import { buildSeparatedPrompt, buildCacheOptimizedPrompt } from "../separated-prompt.js";
3
3
  import { messageContentText } from "./messageText.js";
4
+ import { computeContentDigest } from "../../../src/dedup/digest.js";
5
+ const lastTurnPrefix = new WeakMap();
4
6
  /**
5
7
  * Build the tail injection closure. Returns undefined when nothing is staged
6
8
  * (or the flags are OFF) so the caller falls through to its normal return.
@@ -25,20 +27,51 @@ export function buildTailResult(runtime, config, messages) {
25
27
  else if (config.messageSeparation) {
26
28
  result = buildSeparatedPrompt(result);
27
29
  }
28
- // P2.5: log prefix stability (fire-and-forget, non-fatal).
29
- // tailResult is sync, so use .then().catch() on the dynamic import.
30
+ // P2.5: log cross-turn stable-prefix length (cache-hit proxy). After the
31
+ // prompt is built (separated or cache-optimized), fingerprint each leading
32
+ // message and count how many are byte-identical to the previous turn's
33
+ // prompt, in order. A high stablePrefix = the provider KV-cache prefix is
34
+ // re-used (cache hit). Fire-and-forget + non-fatal.
35
+ //
36
+ // tailResult may run several times per turn (gate return / replay /
37
+ // debounce / live-trim), so the sessionId+turn guard measures once per
38
+ // turn — compare against the PREVIOUS turn's stored footprint, then store
39
+ // this turn's for the next comparison. Cross-session compares are skipped
40
+ // via the stored sessionId check (belt-and-suspenders; the WeakMap entry
41
+ // is scoped to this runtime, which owns the session).
30
42
  if (result.length > 1) {
31
- import("../../../src/cache-stripe-impl.js").then(({ computeStabilityScore }) => {
32
- const stableScore = computeStabilityScore({ content: messageContentText(result[0] ?? result[0]), chunkId: "prefix", accessCount: 0, lastAccessedAt: 0 }, result.slice(0, 2).map((m) => ({ content: messageContentText(m), chunkId: "prefix", accessCount: 0, lastAccessedAt: 0 })));
33
- runtime.logger.info("prefix_stability", {
34
- stableScore: Number.isFinite(stableScore) ? stableScore : 0,
35
- prefixMessages: result.length,
36
- separation: config.messageSeparation ? "v2" : "off",
37
- striping: config.cacheStriping ? "v3" : "off",
38
- });
39
- }).catch(() => {
43
+ try {
44
+ const sessionId = runtime.rt.sessionId;
45
+ const turn = runtime.currentTurn;
46
+ const prev = lastTurnPrefix.get(runtime);
47
+ const isNewTurn = !prev || prev.sessionId !== sessionId || prev.turn !== turn;
48
+ if (isNewTurn) {
49
+ const fingerprints = result.map((m) =>
50
+ // Role prefix disambiguates identical text across variants
51
+ // (roles are a fixed enum — no realistic fingerprint collision).
52
+ computeContentDigest(`${m.role}|${messageContentText(m)}`).contentHash);
53
+ let stablePrefix = 0;
54
+ if (prev && prev.sessionId === sessionId) {
55
+ for (let i = 0; i < fingerprints.length; i++) {
56
+ if (i >= prev.fingerprints.length ||
57
+ prev.fingerprints[i] !== fingerprints[i]) {
58
+ break;
59
+ }
60
+ stablePrefix++;
61
+ }
62
+ }
63
+ lastTurnPrefix.set(runtime, { sessionId, turn, fingerprints });
64
+ runtime.logger.info("prefix_stability", {
65
+ stablePrefix,
66
+ totalMessages: result.length,
67
+ separation: config.messageSeparation ? "v2" : "off",
68
+ striping: config.cacheStriping ? "v3" : "off",
69
+ });
70
+ }
71
+ }
72
+ catch {
40
73
  // Non-fatal: stability logging is best-effort.
41
- });
74
+ }
42
75
  }
43
76
  return { messages: result };
44
77
  };
@@ -17,6 +17,7 @@ export function buildDashboardSnapshot(ctx) {
17
17
  tier: ctx.pressureBand,
18
18
  presetTier: ctx.config.tier,
19
19
  pressure: ctx.pressure,
20
+ vcObserverSamples: ctx.vcObserverSamples ?? 0,
20
21
  config: {
21
22
  fastGatePct: ctx.config.fastGatePct,
22
23
  thresholdTokens: ctx.effectiveThreshold,
@@ -73,17 +74,17 @@ export function buildDashboardSnapshot(ctx) {
73
74
  repo: ctx.repo,
74
75
  compression: {
75
76
  session: {
76
- tokensIn: ctx.rt.tokensSaved + (ctx.st.totalTokenEstimate - ctx.st.originalTokens),
77
+ tokensIn: ctx.st.originalTokens,
77
78
  tokensOut: ctx.st.totalTokenEstimate,
78
79
  tokensFreed: ctx.rt.tokensSaved,
79
- compressionPct: ctx.rt.tokensSaved / Math.max(1, ctx.rt.tokensSaved + (ctx.st.totalTokenEstimate - ctx.st.originalTokens)),
80
+ compressionPct: ctx.rt.tokensSaved / Math.max(1, ctx.st.originalTokens),
80
81
  dedupPct: ctx.rt.dedupAttempts > 0 ? ctx.rt.dedupSkips / ctx.rt.dedupAttempts : 0,
81
82
  },
82
83
  repo: {
83
- tokensIn: ctx.repo.tokensSaved + (ctx.repo.totalTokenEstimate - ctx.repo.originalTokens),
84
+ tokensIn: ctx.repo.originalTokens,
84
85
  tokensOut: ctx.repo.totalTokenEstimate,
85
86
  tokensFreed: ctx.repo.tokensSaved,
86
- compressionPct: ctx.repo.tokensSaved / Math.max(1, ctx.repo.tokensSaved + (ctx.repo.totalTokenEstimate - ctx.repo.originalTokens)),
87
+ compressionPct: ctx.repo.tokensSaved / Math.max(1, ctx.repo.originalTokens),
87
88
  dedupPct: ctx.repo.dedupAttempts > 0 ? ctx.repo.dedupCollapsed / ctx.repo.dedupAttempts : 0,
88
89
  },
89
90
  },
@@ -96,7 +97,9 @@ export function buildDashboardSnapshot(ctx) {
96
97
  // per-session in rt and there's no cumulative counter yet.
97
98
  total: ctx.repo.dedupCollapsed,
98
99
  sessionTokensSaved: ctx.rt.cacheHitTokens,
99
- totalTokensSaved: ctx.repo.dedupCollapsed > 0 ? ctx.repo.dedupCollapsed * 100 : 0,
100
+ // Cumulative tokens saved across all sessions in this repo —
101
+ // was a placeholder (dedupCollapsed * 100).
102
+ totalTokensSaved: ctx.repo.tokensSaved,
100
103
  },
101
104
  compacts: {
102
105
  session: ctx.rt.compactCount,
@@ -109,7 +112,7 @@ export function buildDashboardSnapshot(ctx) {
109
112
  },
110
113
  cacheHit: {
111
114
  sessionSec: ctx.rt.cacheHitTokens / 1000,
112
- totalSec: (ctx.repo.dedupCollapsed * 100) / 1000,
115
+ totalSec: ctx.repo.tokensSaved / 1000,
113
116
  },
114
117
  },
115
118
  model: ctx.currentModel
@@ -22,6 +22,7 @@ import { latestModelSnapshot, readLatestCacheHitPct, recordPerfSample, recordSes
22
22
  import { resolveRepoRoot, } from "../mega-config.js";
23
23
  import { computeMegaSnapshot } from "./snapshot.js";
24
24
  import { buildDashboardSnapshot } from "./dashboard-snapshot.js";
25
+ import { getVcObserver } from "./vc-observer.js";
25
26
  import { materialSigImpl, embedderNameImpl, driftStatusImpl, getTurnLevelImpl, } from "./runtime-helpers.js";
26
27
  // -------------------------------------------------------------- snapshotImpl
27
28
  /** Collect live state and write it to disk (+ paint the above-editor widget).
@@ -85,6 +86,7 @@ export function snapshotImpl(self, ctx) {
85
86
  pressureBand: self.pressureBand,
86
87
  pressure: self.pressure,
87
88
  effectiveThreshold: self.effectiveThreshold,
89
+ vcObserverSamples: getVcObserver(self)?.rows().length ?? 0,
88
90
  statusKey: self.statusKey,
89
91
  lastCtxTokens: self.lastCtxTokens,
90
92
  lastCtxPercent: self.lastCtxPercent,
@@ -0,0 +1,98 @@
1
+ /**
2
+ * vc-observer.ts — VC0A mode-A eval observer wiring for MegaRuntime.
3
+ *
4
+ * Bridges the pure in-memory EvalObserver (src/vector-cortex/eval/observer.ts)
5
+ * into the production runtime: every recorded compact/recall latency sample is
6
+ * both emitted to events.log (via MegaRuntime.appendEvent) and appended to the
7
+ * redacted evaluation.jsonl (via appendEvalRow) so the Vector Cortex dashboard
8
+ * "Observer" histogram reads real data instead of an empty file.
9
+ *
10
+ * All wiring is gated on VC0A_ENABLED() (MEGACOMPACT_VC0A). Flag-off and any
11
+ * construction/persistence error are non-fatal (PREVENT-PI-004: local FS only,
12
+ * no network) and degrade to a null observer — byte-identical to the
13
+ * predecessor (mode C). PREVENT-011: no `any` type.
14
+ *
15
+ * Follows the capture-model.ts / append-event.ts pattern: a context interface +
16
+ * free functions + a thin delegate. No import-time side effects — merely
17
+ * importing these functions is inert regardless of the flag.
18
+ */
19
+ import { VC0A_ENABLED } from "../../src/config.js";
20
+ import { createEvalObserver } from "../../src/vector-cortex/eval/observer.js";
21
+ import { appendEvalRow } from "../../src/vector-cortex/eval/persist.js";
22
+ // ------------------------------------------------------------- construction
23
+ /**
24
+ * Build the production-wired eval observer (mode A) if the VC0A flag is on.
25
+ * Returns null when the flag is off or on any construction error (non-fatal).
26
+ * The returned observer is a thin, best-effort observability seam that never
27
+ * breaks the agent loop.
28
+ */
29
+ export function createVcObserver(self) {
30
+ if (!VC0A_ENABLED())
31
+ return null;
32
+ try {
33
+ return createEvalObserver({
34
+ emit: self.appendEvent.bind(self),
35
+ persist: (sample) => appendEvalRow(self.currentStateDir, [sample]),
36
+ });
37
+ }
38
+ catch {
39
+ /* non-fatal observability — never break the agent loop */
40
+ return null;
41
+ }
42
+ }
43
+ // ---------------------------------------------------------------- weak cache
44
+ /**
45
+ * Module-level observer cache keyed by the runtime object. Avoids adding a
46
+ * `vcObserver` field to MegaRuntime (keeping runtime.ts under its soft limit
47
+ * — no field, no constructor line, no import bloat). One observer per runtime
48
+ * instance, lazily built on the first latency recording.
49
+ */
50
+ const observerCache = new WeakMap();
51
+ /** Lazily create + cache the observer for the given runtime context. */
52
+ export function getVcObserver(self) {
53
+ let obs = observerCache.get(self);
54
+ if (obs === undefined) {
55
+ obs = createVcObserver(self);
56
+ observerCache.set(self, obs);
57
+ }
58
+ return obs;
59
+ }
60
+ // ---------------------------------------------------------------- reporting
61
+ /** Record a compact latency sample on the observer (no-op when absent). */
62
+ export function recordCompactLatency(self, elapsedMs, sessionId, epoch) {
63
+ const obs = getVcObserver(self);
64
+ if (obs == null)
65
+ return;
66
+ try {
67
+ obs.record({
68
+ session: sessionId,
69
+ seq: epoch,
70
+ event: "compact",
71
+ value: elapsedMs,
72
+ unit: "ms",
73
+ mode: "A",
74
+ });
75
+ }
76
+ catch {
77
+ /* non-fatal observability */
78
+ }
79
+ }
80
+ /** Record a recall latency sample on the observer (no-op when absent). */
81
+ export function recordRecallLatency(self, elapsedMs, sessionId, epoch) {
82
+ const obs = getVcObserver(self);
83
+ if (obs == null)
84
+ return;
85
+ try {
86
+ obs.record({
87
+ session: sessionId,
88
+ seq: epoch,
89
+ event: "recall",
90
+ value: elapsedMs,
91
+ unit: "ms",
92
+ mode: "A",
93
+ });
94
+ }
95
+ catch {
96
+ /* non-fatal observability */
97
+ }
98
+ }