pi-mega-compact 0.20.22 → 0.20.23

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 (92) hide show
  1. package/dist/extensions/dashboard-server/resolve-snapshot.js +42 -28
  2. package/dist/extensions/dashboard-server/routes-setup.js +4 -1
  3. package/dist/extensions/dashboard-server/routes-vector-cortex-crystals.js +11 -3
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-diagnostics.js +7 -2
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-economics.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +18 -6
  7. package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +1 -0
  8. package/dist/extensions/dashboard-server/routes-vector-cortex-reconstruct.js +10 -3
  9. package/dist/extensions/dashboard-server/routes-vector-cortex-repair.js +13 -5
  10. package/dist/extensions/dashboard-server/routes-vector-cortex-shards.js +12 -4
  11. package/dist/extensions/dashboard-server/vc-event-counts.js +63 -0
  12. package/dist/extensions/mega-events/context-handler/afterCompact.js +34 -0
  13. package/dist/extensions/mega-events/context-handler/pipelineRun.js +33 -0
  14. package/dist/extensions/mega-runtime/dashboard-snapshot.js +9 -6
  15. package/dist/extensions/mega-runtime/runtime-snapshot.js +2 -0
  16. package/dist/extensions/mega-runtime/vc-observer.js +98 -0
  17. package/extensions/dashboard-client/dist/assets/{AreaChart-BkfB-LSQ.js → AreaChart-BzEedilo.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{AreaChart-BkfB-LSQ.js.map → AreaChart-BzEedilo.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{BarChart-Cs1kogko.js → BarChart-CzAVg1_J.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{BarChart-Cs1kogko.js.map → BarChart-CzAVg1_J.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{CacheTab-BZgaguGr.js → CacheTab-D2-rzTuX.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{CacheTab-BZgaguGr.js.map → CacheTab-D2-rzTuX.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{EventsTab-BFbkI_MO.js → EventsTab-DB7g1Szy.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{EventsTab-BFbkI_MO.js.map → EventsTab-DB7g1Szy.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{HealthTab-CvlOENCX.js → HealthTab-Cvkmi-R3.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{HealthTab-CvlOENCX.js.map → HealthTab-Cvkmi-R3.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CtBuz2qL.js → MaintenanceTab-g_udcOO7.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CtBuz2qL.js.map → MaintenanceTab-g_udcOO7.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-K4775S4d.js → MemoryMapTab-DAjBaf8P.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-K4775S4d.js.map → MemoryMapTab-DAjBaf8P.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{MetricsTab-DVGcFInz.js → MetricsTab-13dYYocB.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{MetricsTab-DVGcFInz.js.map → MetricsTab-13dYYocB.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{OverviewTab-4LmW5ZCl.js → OverviewTab-CLpCyLvx.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{OverviewTab-4LmW5ZCl.js.map → OverviewTab-CLpCyLvx.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{ReposTab-StebchgQ.js → ReposTab-EdAqUrwG.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{ReposTab-StebchgQ.js.map → ReposTab-EdAqUrwG.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{SessionsTab-C3D_XAkX.js → SessionsTab-C21ZuOL_.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{SessionsTab-C3D_XAkX.js.map → SessionsTab-C21ZuOL_.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/SetupTab-CP7lE3L_.js +2 -0
  40. package/extensions/dashboard-client/dist/assets/SetupTab-CP7lE3L_.js.map +1 -0
  41. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CTPi_AAW.js → TimeSavedCard-DZ3QR8qt.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CTPi_AAW.js.map → TimeSavedCard-DZ3QR8qt.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{TurnsTab-DSOGcY5Q.js → TurnsTab-CNchE5rD.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{TurnsTab-DSOGcY5Q.js.map → TurnsTab-CNchE5rD.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-OMW8_RFf.js → VectorCortexTab-B3WuPeyn.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-OMW8_RFf.js.map → VectorCortexTab-B3WuPeyn.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{WikiTab-Db-qFDdg.js → WikiTab-BIe5Z9Nc.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{WikiTab-Db-qFDdg.js.map → WikiTab-BIe5Z9Nc.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{button-BF4UgBAD.js → button-BrVreSt5.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{button-BF4UgBAD.js.map → button-BrVreSt5.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{card-XmXoYQqB.js → card-D9F4wbqs.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{card-XmXoYQqB.js.map → card-D9F4wbqs.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Be8hM1aU.js → generateCategoricalChart-mkdzCnMP.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Be8hM1aU.js.map → generateCategoricalChart-mkdzCnMP.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{index-NXDgILxv.js → index-CtlmOlj1.js} +3 -3
  56. package/extensions/dashboard-client/dist/assets/{index-NXDgILxv.js.map → index-CtlmOlj1.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{switch-kUZCrbia.js → switch-BXmp5iwA.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{switch-kUZCrbia.js.map → switch-BXmp5iwA.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{toggle-DEe5mCwF.js → toggle-BgawNwW9.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{toggle-DEe5mCwF.js.map → toggle-BgawNwW9.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{useSSE-BlCyLqge.js → useSSE-DZKX73Ea.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{useSSE-BlCyLqge.js.map → useSSE-DZKX73Ea.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/index.html +1 -1
  64. package/extensions/dashboard-client/src/tabs/SetupTab/CustomEndpointSection.tsx +65 -0
  65. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +19 -108
  66. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetupStyles.ts +69 -0
  67. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +5 -3
  68. package/extensions/dashboard-server/api-contracts/setup.ts +4 -3
  69. package/extensions/dashboard-server/api-contracts/vector-cortex-cache.ts +5 -0
  70. package/extensions/dashboard-server/api-contracts/vector-cortex-diagnostics.ts +2 -0
  71. package/extensions/dashboard-server/api-contracts/vector-cortex-economics.ts +2 -0
  72. package/extensions/dashboard-server/api-contracts/vector-cortex-policy.ts +2 -0
  73. package/extensions/dashboard-server/resolve-snapshot.ts +42 -28
  74. package/extensions/dashboard-server/routes-setup.ts +3 -1
  75. package/extensions/dashboard-server/routes-vector-cortex-crystals.ts +12 -3
  76. package/extensions/dashboard-server/routes-vector-cortex-diagnostics.ts +8 -2
  77. package/extensions/dashboard-server/routes-vector-cortex-economics.ts +1 -0
  78. package/extensions/dashboard-server/routes-vector-cortex-heal.ts +19 -6
  79. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +1 -0
  80. package/extensions/dashboard-server/routes-vector-cortex-reconstruct.ts +11 -3
  81. package/extensions/dashboard-server/routes-vector-cortex-repair.ts +14 -5
  82. package/extensions/dashboard-server/routes-vector-cortex-shards.ts +13 -4
  83. package/extensions/dashboard-server/vc-event-counts.ts +67 -0
  84. package/extensions/mega-dashboard.ts +2 -0
  85. package/extensions/mega-events/context-handler/afterCompact.ts +46 -0
  86. package/extensions/mega-events/context-handler/pipelineRun.ts +39 -0
  87. package/extensions/mega-runtime/dashboard-snapshot.ts +12 -6
  88. package/extensions/mega-runtime/runtime-snapshot.ts +6 -0
  89. package/extensions/mega-runtime/vc-observer.ts +120 -0
  90. package/package.json +1 -1
  91. package/extensions/dashboard-client/dist/assets/SetupTab-BTBGyppF.js +0 -2
  92. package/extensions/dashboard-client/dist/assets/SetupTab-BTBGyppF.js.map +0 -1
@@ -8,16 +8,14 @@
8
8
  * Resolution order (the launch dir is the OWNer of this dashboard):
9
9
  * 1. The launch stateDir's OWN dashboard.json — the pi process that launched
10
10
  * this dashboard server is the session you are coding in, so its snapshot
11
- * is "this session" by definition. This must win over any other repo
12
- * regardless of last_seen freshness; otherwise a more-recently-active
13
- * OTHER repo steals the "this session" slot and your actual session gets
14
- * demoted to an "other" gauge, producing the exact duplicate-gauge bug
15
- * (this session + pi-<launchrepo> both showing the same active session
16
- * split across two boxes).
17
- * 2. Most recently seen OTHER repo (by last_seen in repo_registry) whose
18
- * dashboard.json exists and has a non-null updatedAt — fallback for when
19
- * the launch dir has no live snapshot yet (e.g. dashboard launched before
20
- * any compaction wrote dashboard.json).
11
+ * is "this session" by definition. It must be fresh (updatedAt within
12
+ * STALE_MS) to win; otherwise the launch dir's pi process has exited or
13
+ * stopped snapshotting, and showing its stale data as "this session" is
14
+ * misleading. When stale, fall through to the most-recently-active repo.
15
+ * 2. Most recently seen repo (by last_seen in repo_registry) whose
16
+ * dashboard.json exists and has a non-null updatedAt — this includes the
17
+ * launch dir via the registry, so an exited-but-recent launch session
18
+ * still appears.
21
19
  * 3. The static launch stateDir's dashboard.json (original behavior).
22
20
  */
23
21
  import { existsSync } from "node:fs";
@@ -25,14 +23,29 @@ import { join } from "node:path";
25
23
  import { createRequire } from "node:module";
26
24
  import { readSnapshot } from "./snapshot.js";
27
25
  import { getIndexDir } from "./index-reader.js";
26
+ /** Snapshots older than this are stale — the owning pi session has exited or
27
+ * stopped snapshotting. 5 minutes covers the slowest possible context event
28
+ * cadence + debounce + snapshot throttle. */
29
+ const STALE_MS = 5 * 60 * 1000;
30
+ /** Age of a snapshot's updatedAt in ms. Returns Infinity for invalid dates. */
31
+ function snapshotAgeMs(updatedAt) {
32
+ if (!updatedAt)
33
+ return Infinity;
34
+ const t = Date.parse(updatedAt);
35
+ if (Number.isNaN(t))
36
+ return Infinity;
37
+ return Date.now() - t;
38
+ }
28
39
  const require = createRequire(import.meta.url);
29
40
  /**
30
- * Find the most recently active OTHER repo's stateDir (excluding the launch
31
- * dir, which is handled by the caller) by scanning the repo registry for the
32
- * repo with the highest last_seen whose dashboard.json exists and has a
33
- * non-null updatedAt. Returns null if no suitable repo is found.
41
+ * Find the most recently active repo's stateDir by scanning the repo registry
42
+ * for the repo with the highest last_seen whose dashboard.json exists and has
43
+ * a non-null updatedAt. Unlike the old `mostRecentOtherRepoStateDir`, this
44
+ * does NOT exclude the launch dir when the launch dir's snapshot is stale
45
+ * but its registry entry has a recent last_seen (e.g. session just exited),
46
+ * it should still be eligible.
34
47
  */
35
- function mostRecentOtherRepoStateDir(launchStateDir) {
48
+ function mostRecentActiveRepoStateDir(_launchStateDir) {
36
49
  const indexPath = join(getIndexDir(), "index.sqlite");
37
50
  if (!existsSync(indexPath))
38
51
  return null;
@@ -43,9 +56,8 @@ function mostRecentOtherRepoStateDir(launchStateDir) {
43
56
  const rows = db
44
57
  .prepare(`SELECT state_dir, last_seen FROM repo_registry
45
58
  WHERE state_dir IS NOT NULL AND last_seen IS NOT NULL
46
- AND state_dir != @launch
47
59
  ORDER BY last_seen DESC`)
48
- .all({ launch: launchStateDir });
60
+ .all();
49
61
  for (const r of rows) {
50
62
  const dashPath = join(r.state_dir, "dashboard.json");
51
63
  if (!existsSync(dashPath))
@@ -70,24 +82,26 @@ function mostRecentOtherRepoStateDir(launchStateDir) {
70
82
  }
71
83
  }
72
84
  /**
73
- * Resolve the best snapshot to serve. Tries the most recently active repo's
74
- * dashboard.json first, then falls back to the static launch path.
85
+ * Resolve the best snapshot to serve. The launch dir wins when its snapshot is
86
+ * fresh; when stale (or missing) the most recently active repo wins instead.
75
87
  */
76
88
  export function resolveSnapshot(staticSnapshotPath, staticStateDir) {
77
- // 1. The launch dir's OWN snapshot is "this session" — the pi process that
78
- // launched this dashboard is the session you are coding in. It wins over
79
- // any other repo regardless of last_seen, so a more-recently-active OTHER
80
- // repo can't steal the "this session" slot and demote your actual session
81
- // to a duplicate "other" gauge.
89
+ // 1. The launch dir's OWN snapshot is "this session" — but only when it's
90
+ // fresh (updated within STALE_MS). A stale launch-dir snapshot means
91
+ // the pi process that owned it has exited or stopped snapshotting; serving
92
+ // its frozen data as "this session" is misleading. Fall through to the
93
+ // most-recently-active repo so the user sees the session that is actually
94
+ // running.
82
95
  if (existsSync(staticSnapshotPath)) {
83
96
  const launchSnap = readSnapshot(staticSnapshotPath);
84
- if (launchSnap.updatedAt) {
97
+ if (launchSnap.updatedAt && snapshotAgeMs(launchSnap.updatedAt) < STALE_MS) {
85
98
  return { snapshot: launchSnap, stateDir: staticStateDir };
86
99
  }
87
100
  }
88
- // 2. Fall back to the most recently active OTHER repo (launch dir had no
89
- // live snapshot yet).
90
- const activeDir = mostRecentOtherRepoStateDir(staticStateDir);
101
+ // 2. Fall back to the most recently active OTHER repo (launch dir stale or
102
+ // had no live snapshot yet). The registry includes the launch dir too,
103
+ // so a recently-exited launch session can still appear.
104
+ const activeDir = mostRecentActiveRepoStateDir(staticStateDir);
91
105
  if (activeDir) {
92
106
  const activePath = join(activeDir, "dashboard.json");
93
107
  const snap = readSnapshot(activePath);
@@ -193,6 +193,7 @@ export function handleSetupDetect(req, res, _ctx) {
193
193
  // ---------------------------------------------------------------------------
194
194
  const OLLAMA_DEFAULT_URL = "http://localhost:11434/api/embeddings"; // guardrails-allow PREVENT-PI-004: localhost-only config string, not a runtime fetch
195
195
  const LLAMA_DEFAULT_URL = "http://localhost:8080/v1/embeddings"; // guardrails-allow PREVENT-PI-004: localhost-only config string, not a runtime fetch
196
+ const ONNX_DEFAULT_URL = "http://localhost:8081/v1/embeddings"; // guardrails-allow PREVENT-PI-004: localhost-only config string, not a runtime fetch — TEI / ONNX embedding server
196
197
  function readJsonBody(req, cb) {
197
198
  let body = "";
198
199
  let tooBig = false;
@@ -237,7 +238,7 @@ export function handleSetupConfigure(req, res, ctx) {
237
238
  }
238
239
  const body = parsed.value;
239
240
  const embedder = body.embedder;
240
- if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram" && embedder !== "custom") {
241
+ if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram" && embedder !== "custom" && embedder !== "onnx") {
241
242
  res.writeHead(400, { "Content-Type": "application/json" });
242
243
  res.end(JSON.stringify({ error: "invalid_embedder" }));
243
244
  return;
@@ -249,6 +250,8 @@ export function handleSetupConfigure(req, res, ctx) {
249
250
  resolvedUrl = typeof url === "string" && url ? url : OLLAMA_DEFAULT_URL;
250
251
  else if (embedder === "llama")
251
252
  resolvedUrl = typeof url === "string" && url ? url : LLAMA_DEFAULT_URL;
253
+ else if (embedder === "onnx")
254
+ resolvedUrl = typeof url === "string" && url ? url : ONNX_DEFAULT_URL;
252
255
  else if (embedder === "custom") {
253
256
  // Third-party / remote endpoint. Requires a URL; opts in to the
254
257
  // non-loopback allowlist so embeddingConfigFromEnv() accepts it.
@@ -25,13 +25,19 @@
25
25
  */
26
26
  import { VC7A_ENABLED } from "../../src/config.js";
27
27
  import { sendJson } from "./routes-vector-cortex-shared.js";
28
+ import { countVcEvents, vcCount } from "./vc-event-counts.js";
29
+ // Actual events emitted by src/vector-cortex/cache/crystal-emit.ts.
30
+ const CRYSTAL_EVENTS = [
31
+ "vector_cortex_crystal_written",
32
+ "vector_cortex_crystal_collision",
33
+ ];
28
34
  /**
29
35
  * Reader-only GET /api/vector-cortex/cache-crystals (VC7A).
30
36
  *
31
37
  * Counts, byte volumes, and CRY_* codes only — a static reader-only aggregate
32
38
  * seam with the same shape as the VC6A/VC6B/VC6C handlers.
33
39
  */
34
- export function handleVectorCortexCrystals(req, res, _ctx) {
40
+ export function handleVectorCortexCrystals(req, res, ctx) {
35
41
  const url = req.url ?? "";
36
42
  const path = url.split("?")[0] ?? url;
37
43
  if (path !== "/api/vector-cortex/cache-crystals")
@@ -41,6 +47,7 @@ export function handleVectorCortexCrystals(req, res, _ctx) {
41
47
  return true;
42
48
  }
43
49
  const enabled = VC7A_ENABLED();
50
+ const counts = countVcEvents(ctx.stateDir, CRYSTAL_EVENTS);
44
51
  // Flag-off routes to mode C: with VC7A off nothing is served from the crystal
45
52
  // cache, which is exactly the spec's "cache bypass" outcome. Reporting A (hit)
46
53
  // or B (fresh render forced by a miss) would imply a cache path that is not
@@ -49,16 +56,17 @@ export function handleVectorCortexCrystals(req, res, _ctx) {
49
56
  const body = {
50
57
  enabled,
51
58
  mode,
52
- crystalCount: 0,
59
+ crystalCount: vcCount(counts, "vector_cortex_crystal_written"),
53
60
  totalBytes: 0,
54
61
  hits: 0,
55
62
  misses: 0,
56
63
  hitBytes: 0,
57
64
  writes: 0,
58
65
  duplicateWrites: 0,
59
- collisions: 0,
66
+ collisions: vcCount(counts, "vector_cortex_crystal_collision"),
60
67
  lastFailure: null,
61
68
  updatedAt: new Date().toISOString(),
69
+ deferredReason: "crystal_store_not_instantiated_v0_20_23",
62
70
  };
63
71
  sendJson(res, 200, body);
64
72
  return true;
@@ -27,13 +27,16 @@
27
27
  */
28
28
  import { VC7C_ENABLED } from "../../src/config.js";
29
29
  import { sendJson } from "./routes-vector-cortex-shared.js";
30
+ import { countVcEvents, vcCount } from "./vc-event-counts.js";
31
+ // Actual events emitted by src/vector-cortex/cache/diagnostics-emit.ts.
32
+ const DIAGNOSTICS_EVENTS = ["vector_cortex_cache_serve_blocked"];
30
33
  /**
31
34
  * Reader-only GET /api/vector-cortex/cache-diagnostics (VC7C).
32
35
  *
33
36
  * Per-miss-class counts, breaker state, and CACHE and M5 codes only — a static
34
37
  * reader-only aggregate seam with the same shape as the VC7B economics handler.
35
38
  */
36
- export function handleVectorCortexDiagnostics(req, res, _ctx) {
39
+ export function handleVectorCortexDiagnostics(req, res, ctx) {
37
40
  const url = req.url ?? "";
38
41
  const path = url.split("?")[0] ?? url;
39
42
  if (path !== "/api/vector-cortex/cache-diagnostics")
@@ -43,6 +46,7 @@ export function handleVectorCortexDiagnostics(req, res, _ctx) {
43
46
  return true;
44
47
  }
45
48
  const enabled = VC7C_ENABLED();
49
+ const counts = countVcEvents(ctx.stateDir, DIAGNOSTICS_EVENTS);
46
50
  // Flag-off routes to mode C: with VC7C off the diagnostics/breaker reporter is
47
51
  // suppressed, so no cache serve is attested here and the surface reports the
48
52
  // all-cache bypass outcome. Reporting A (crystal served) or B (fresh render
@@ -58,10 +62,11 @@ export function handleVectorCortexDiagnostics(req, res, _ctx) {
58
62
  requestMisses: 0,
59
63
  generationMisses: 0,
60
64
  unknownMisses: 0,
61
- serveBlocked: 0,
65
+ serveBlocked: vcCount(counts, "vector_cortex_cache_serve_blocked"),
62
66
  breakerState: "closed",
63
67
  lastFailure: null,
64
68
  updatedAt: new Date().toISOString(),
69
+ deferredReason: "cache_classifier_not_wired_v0_20_23",
65
70
  };
66
71
  sendJson(res, 200, body);
67
72
  return true;
@@ -53,6 +53,7 @@ export function handleVectorCortexEconomics(req, res, _ctx) {
53
53
  unprovenExclusions: 0,
54
54
  lastFailure: null,
55
55
  updatedAt: new Date().toISOString(),
56
+ deferredReason: "economics_not_computed_v0_20_23",
56
57
  };
57
58
  sendJson(res, 200, body);
58
59
  return true;
@@ -21,8 +21,18 @@
21
21
  */
22
22
  import { VC6A_ENABLED, VC6B_ENABLED } from "../../src/config.js";
23
23
  import { sendJson } from "./routes-vector-cortex-shared.js";
24
+ import { countVcEvents, vcCount } from "./vc-event-counts.js";
25
+ // Actual event names emitted by src/vector-cortex/heal/emit.ts + restore-emit.ts.
26
+ const CLOSURE_EVENTS = [
27
+ "vector_cortex_closure_optimized",
28
+ "vector_cortex_closure_proof_rejected",
29
+ ];
30
+ const RESTORE_EVENTS = [
31
+ "vector_cortex_source_restored",
32
+ "vector_cortex_restore_digest_rejected",
33
+ ];
24
34
  /** Reader-only GET /api/vector-cortex/closure-proof (VC6A). */
25
- export function handleVectorCortexClosureProof(req, res, _ctx) {
35
+ export function handleVectorCortexClosureProof(req, res, ctx) {
26
36
  const url = req.url ?? "";
27
37
  const path = url.split("?")[0] ?? url;
28
38
  if (path !== "/api/vector-cortex/closure-proof")
@@ -32,13 +42,14 @@ export function handleVectorCortexClosureProof(req, res, _ctx) {
32
42
  return true;
33
43
  }
34
44
  const enabled = VC6A_ENABLED();
45
+ const counts = countVcEvents(ctx.stateDir, CLOSURE_EVENTS);
35
46
  // Flag-off routes to mode B (conservative closure, no reduction) per spec.
36
47
  const mode = enabled ? "A" : "B";
37
48
  const body = {
38
49
  enabled,
39
50
  mode,
40
- optimizations: 0,
41
- proofRejections: 0,
51
+ optimizations: vcCount(counts, "vector_cortex_closure_optimized"),
52
+ proofRejections: vcCount(counts, "vector_cortex_closure_proof_rejected"),
42
53
  retainedEdgeTotal: 0,
43
54
  removedEdgeTotal: 0,
44
55
  conservativeTraversalTotal: 0,
@@ -56,7 +67,7 @@ export function handleVectorCortexClosureProof(req, res, _ctx) {
56
67
  * seam (same shape as the VC6A handler): there is no payload endpoint and never
57
68
  * will be, so no restored bytes/span ids/node ids/ledger text can leak here.
58
69
  */
59
- export function handleVectorCortexRestore(req, res, _ctx) {
70
+ export function handleVectorCortexRestore(req, res, ctx) {
60
71
  const url = req.url ?? "";
61
72
  const path = url.split("?")[0] ?? url;
62
73
  if (path !== "/api/vector-cortex/restore")
@@ -66,6 +77,7 @@ export function handleVectorCortexRestore(req, res, _ctx) {
66
77
  return true;
67
78
  }
68
79
  const enabled = VC6B_ENABLED();
80
+ const counts = countVcEvents(ctx.stateDir, RESTORE_EVENTS);
69
81
  // Flag-off routes to mode C: with VC6B off there is no exact-restoration path
70
82
  // at all, so the honest OFF view is the disclose-loss mode (old context is
71
83
  // omitted rather than inferred) — mirroring how VC6A's OFF view reports the
@@ -74,10 +86,10 @@ export function handleVectorCortexRestore(req, res, _ctx) {
74
86
  const body = {
75
87
  enabled,
76
88
  mode,
77
- restoreAttempts: 0,
89
+ restoreAttempts: vcCount(counts, "vector_cortex_source_restored"),
78
90
  restoredCount: 0,
79
91
  missingCount: 0,
80
- digestRejections: 0,
92
+ digestRejections: vcCount(counts, "vector_cortex_restore_digest_rejected"),
81
93
  lastRejection: null,
82
94
  updatedAt: new Date().toISOString(),
83
95
  };
@@ -39,6 +39,7 @@ export function handleVectorCortexPolicy(req, res, _ctx) {
39
39
  pressureVersion: 1,
40
40
  lastFailure: null,
41
41
  updatedAt: new Date().toISOString(),
42
+ deferredReason: "shadow_controller_not_instantiated_v0_20_23",
42
43
  };
43
44
  sendJson(res, 200, body);
44
45
  return true;
@@ -8,7 +8,13 @@
8
8
  */
9
9
  import { VC4C_ENABLED } from "../../src/config.js";
10
10
  import { sendJson } from "./routes-vector-cortex-shared.js";
11
- export function handleVectorCortexReconstruct(req, res, _ctx) {
11
+ import { countVcEvents, vcCount } from "./vc-event-counts.js";
12
+ // Closure attempt/rejection events emitted by src/vector-cortex/heal/emit.ts.
13
+ const RECONSTRUCT_EVENTS = [
14
+ "vector_cortex_closure_optimized",
15
+ "vector_cortex_closure_proof_rejected",
16
+ ];
17
+ export function handleVectorCortexReconstruct(req, res, ctx) {
12
18
  const url = req.url ?? "";
13
19
  const path = url.split("?")[0] ?? url;
14
20
  if (path !== "/api/vector-cortex/reconstruct")
@@ -18,10 +24,11 @@ export function handleVectorCortexReconstruct(req, res, _ctx) {
18
24
  return true;
19
25
  }
20
26
  const enabled = VC4C_ENABLED();
27
+ const counts = countVcEvents(ctx.stateDir, RECONSTRUCT_EVENTS);
21
28
  const body = {
22
29
  enabled,
23
- closureAttempts: 0,
24
- closureRejections: 0,
30
+ closureAttempts: vcCount(counts, "vector_cortex_closure_optimized"),
31
+ closureRejections: vcCount(counts, "vector_cortex_closure_proof_rejected"),
25
32
  validatedCount: 0,
26
33
  invalidatedCount: 0,
27
34
  spanTotal: 0,
@@ -22,6 +22,13 @@
22
22
  */
23
23
  import { VC6C_ENABLED } from "../../src/config.js";
24
24
  import { sendJson } from "./routes-vector-cortex-shared.js";
25
+ import { countVcEvents, vcCount } from "./vc-event-counts.js";
26
+ // Actual event names emitted by src/vector-cortex/heal/repair-emit.ts.
27
+ const REPAIR_EVENTS = [
28
+ "vector_cortex_repair_planned",
29
+ "vector_cortex_repair_pointer_switched",
30
+ "vector_cortex_repair_backoff",
31
+ ];
25
32
  /**
26
33
  * Reader-only GET /api/vector-cortex/repair (VC6C).
27
34
  *
@@ -30,7 +37,7 @@ import { sendJson } from "./routes-vector-cortex-shared.js";
30
37
  * never will be, so no subsystem bytes, gap ranges, high-water marks, or root
31
38
  * digests can leak here.
32
39
  */
33
- export function handleVectorCortexRepair(req, res, _ctx) {
40
+ export function handleVectorCortexRepair(req, res, ctx) {
34
41
  const url = req.url ?? "";
35
42
  const path = url.split("?")[0] ?? url;
36
43
  if (path !== "/api/vector-cortex/repair")
@@ -40,6 +47,7 @@ export function handleVectorCortexRepair(req, res, _ctx) {
40
47
  return true;
41
48
  }
42
49
  const enabled = VC6C_ENABLED();
50
+ const counts = countVcEvents(ctx.stateDir, REPAIR_EVENTS);
43
51
  // Flag-off routes to mode C: with VC6C off no self-healing controller runs at
44
52
  // all, so neither the targeted rebuild (mode A) nor the full deterministic
45
53
  // rebuild (mode B) path is available. Mode C is exactly the spec's "derived
@@ -50,10 +58,10 @@ export function handleVectorCortexRepair(req, res, _ctx) {
50
58
  const body = {
51
59
  enabled,
52
60
  mode,
53
- repairAttempts: 0,
54
- repairsPlanned: 0,
55
- pointersSwitched: 0,
56
- backoffs: 0,
61
+ repairAttempts: vcCount(counts, "vector_cortex_repair_planned"),
62
+ repairsPlanned: vcCount(counts, "vector_cortex_repair_planned"),
63
+ pointersSwitched: vcCount(counts, "vector_cortex_repair_pointer_switched"),
64
+ backoffs: vcCount(counts, "vector_cortex_repair_backoff"),
57
65
  lastBackoffMs: null,
58
66
  lastFailure: null,
59
67
  updatedAt: new Date().toISOString(),
@@ -19,10 +19,16 @@
19
19
  */
20
20
  import { VC4A_ENABLED } from "../../src/config.js";
21
21
  import { sendJson } from "./routes-vector-cortex-shared.js";
22
+ import { countVcEvents, vcCount } from "./vc-event-counts.js";
23
+ // Actual events emitted by src/vector-cortex/shards/manifest.ts. VC4A has no
24
+ // per-tier (semantic/exact) write event — the manifest build is the single
25
+ // shard-write signal — so semanticCount is wired to it and exactCount stays 0
26
+ // until a per-tier write event exists.
27
+ const SHARD_EVENTS = ["vector_cortex_shard_manifest_built"];
22
28
  /**
23
29
  * Reader-only GET /api/vector-cortex/shards (VC4A).
24
30
  */
25
- export function handleVectorCortexShards(req, res, _ctx) {
31
+ export function handleVectorCortexShards(req, res, ctx) {
26
32
  const url = req.url ?? "";
27
33
  const path = url.split("?")[0] ?? url;
28
34
  if (path !== "/api/vector-cortex/shards")
@@ -33,11 +39,13 @@ export function handleVectorCortexShards(req, res, _ctx) {
33
39
  return true;
34
40
  }
35
41
  const enabled = VC4A_ENABLED();
42
+ const counts = countVcEvents(ctx.stateDir, SHARD_EVENTS);
36
43
  const body = {
37
44
  enabled,
38
- // No durable shard manifest is staged yet in this sprint (pure in-memory
39
- // partition), so the aggregates are truthfully zero.
40
- semanticCount: 0,
45
+ // The manifest-build event is the only VC4A shard-write signal; semanticCount
46
+ // reflects manifests built and exactCount stays 0 (no per-tier write event
47
+ // distinguishes exact shards in events.log yet).
48
+ semanticCount: vcCount(counts, "vector_cortex_shard_manifest_built"),
41
49
  exactCount: 0,
42
50
  byteTotal: 0,
43
51
  protectedByteTotal: 0,
@@ -0,0 +1,63 @@
1
+ /**
2
+ * dashboard-server/vc-event-counts.ts — lightweight events.log tail reader for
3
+ * the Vector Cortex dashboard routes.
4
+ *
5
+ * The VC event reporters in `src/vector-cortex/*` emit structured JSON events
6
+ * (one per line) into the per-repo `events.log`. Instead of the dashboard
7
+ * hardcoding zero for aggregate counters, this module reads the event tail and
8
+ * counts real occurrences of named events. It is the single shared seam so every
9
+ * route stays a small, thin consumer and the counting logic lives in one place
10
+ * (extensions/ 400-line soft-as-hard split trigger).
11
+ *
12
+ * NEVER throws: a missing/unreadable file degrades to an empty Map, malformed
13
+ * lines are skipped silently, and only the `.event` string field is inspected —
14
+ * no payload, bytes, span ids, digests, or ledger text ever leaves this module
15
+ * (SECURITY_PRIVACY, reader-only aggregate).
16
+ *
17
+ * Guardrails: PREVENT-PI-004 (local in-process file read only), PREVENT-011
18
+ * (no `any`).
19
+ */
20
+ import { join } from "node:path";
21
+ import { readFileSync } from "node:fs";
22
+ /**
23
+ * Count occurrences of the given named events within the last `maxLines` of the
24
+ * per-repo events.log adjacent to `stateDir`. Returns a Map keyed by event name;
25
+ * a missing/unreadable log or zero matching lines yields an empty Map. Best-effort
26
+ * and non-fatal by construction (matches the non-fatal-stores invariant).
27
+ */
28
+ export function countVcEvents(stateDir, eventNames, maxLines = 10000) {
29
+ const counts = new Map();
30
+ let raw;
31
+ try {
32
+ raw = readFileSync(join(stateDir, "events.log"), "utf-8");
33
+ }
34
+ catch {
35
+ // Missing or unreadable log → nothing to count, never throw.
36
+ return counts;
37
+ }
38
+ // Walk only the tail: split into lines and keep the last `maxLines`.
39
+ const lines = raw.split("\n");
40
+ const start = Math.max(0, lines.length - maxLines);
41
+ const wanted = new Set(eventNames);
42
+ for (let i = start; i < lines.length; i++) {
43
+ const line = lines[i];
44
+ if (line.length === 0)
45
+ continue;
46
+ let ev = null;
47
+ try {
48
+ ev = JSON.parse(line);
49
+ }
50
+ catch {
51
+ // Malformed line — skip silently, never break the reader.
52
+ continue;
53
+ }
54
+ if (typeof ev?.event === "string" && wanted.has(ev.event)) {
55
+ counts.set(ev.event, (counts.get(ev.event) ?? 0) + 1);
56
+ }
57
+ }
58
+ return counts;
59
+ }
60
+ /** Read a value from an event-count map, defaulting to 0 when absent. */
61
+ export function vcCount(counts, name) {
62
+ return counts.get(name) ?? 0;
63
+ }
@@ -15,6 +15,9 @@ import { openTurnStore } from "../../../src/store/turns/connection.js";
15
15
  import { buildTopicModel, createTopicStore, bumpWikiCompactCounter, applyOverridesAfterRebuild, applyFullOverridesAfterRebuild, } from "../../../src/topics/index.js";
16
16
  import { assignNewMemoriesIncremental } from "../../../src/wiki/index.js";
17
17
  import { TrigramEmbedder } from "../../../src/embedder.js";
18
+ import { reportClosureOptimized } from "../../../src/vector-cortex/heal/emit.js";
19
+ import { reportRepairPlanned } from "../../../src/vector-cortex/heal/repair-emit.js";
20
+ import { VC6A_ENABLED, VC6C_ENABLED } from "../../../src/config/vector-cortex.js";
18
21
  /**
19
22
  * Persist the checkpoint_epoch, stamp turns, rebuild the auto-wiki, seed the
20
23
  * topic model, and dedup — gated on dbMirror. Non-fatal end-to-end.
@@ -193,4 +196,35 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
193
196
  runtime.logger.warn("db-mirror-epoch-fail", { error: String(e) });
194
197
  }
195
198
  }
199
+ // VC6 Heal lifecycle emits (post-compact). Wiring stubs — dashboard graph +
200
+ // event counts move; real gap detection/rebuild is a future sprint.
201
+ // VC6A: closure-optimization savings from the compact token delta.
202
+ const savings = Math.max(0, (ran.result.originalTokenEstimate ?? 0) - (ran.result.tokenEstimate ?? 0));
203
+ if (VC6A_ENABLED()) {
204
+ try {
205
+ reportClosureOptimized((name, payload) => runtime.appendEvent(name, payload), {
206
+ sessionId: runtime.rt.sessionId,
207
+ removed: 0,
208
+ retained: ran.result.compactedFrom,
209
+ savings,
210
+ });
211
+ }
212
+ catch {
213
+ /* non-fatal: VC6A heal emit never breaks compaction */
214
+ }
215
+ }
216
+ // VC6C: repair-planner placeholder (no real gap detection yet).
217
+ if (VC6C_ENABLED()) {
218
+ try {
219
+ reportRepairPlanned((name, payload) => runtime.appendEvent(name, payload), {
220
+ subsystem: "post_compact",
221
+ generation: 1,
222
+ backoffMs: 0,
223
+ gapSize: 0,
224
+ });
225
+ }
226
+ catch {
227
+ /* non-fatal: VC6C heal emit never breaks compaction */
228
+ }
229
+ }
196
230
  }
@@ -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.
@@ -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