pi-mega-compact 0.11.5 → 0.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/cache-stripes.js +8 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +13 -0
  3. package/dist/extensions/dashboard-server/api-contracts/memory-map.js +1 -0
  4. package/dist/extensions/dashboard-server/routes-cache.js +28 -0
  5. package/dist/extensions/dashboard-server/routes-game.js +3 -0
  6. package/dist/extensions/dashboard-server/routes-memory-map.js +50 -0
  7. package/dist/extensions/dashboard-server/routes-setup.js +105 -0
  8. package/dist/extensions/dashboard-server/routes.js +3 -1
  9. package/dist/extensions/dashboard-server/server.js +7 -1
  10. package/dist/extensions/mega-compact.js +5 -0
  11. package/dist/extensions/mega-config.js +5 -0
  12. package/dist/extensions/mega-events/context-handler.js +10 -2
  13. package/dist/extensions/mega-events/perf-handler.js +83 -1
  14. package/dist/extensions/mega-events/separated-prompt.js +248 -0
  15. package/dist/extensions/mega-runtime/env-loader.js +56 -0
  16. package/dist/extensions/mega-runtime/reset-runtime.js +1 -0
  17. package/dist/extensions/mega-runtime/runtime-snapshot.js +1 -0
  18. package/dist/extensions/mega-runtime/runtime.js +1 -0
  19. package/dist/extensions/mega-runtime/snapshot.js +1 -0
  20. package/dist/extensions/mega-runtime/widget.js +5 -0
  21. package/dist/src/cache-stripe-impl.js +288 -0
  22. package/dist/src/cache-stripe.js +11 -0
  23. package/dist/src/config/stopwords.js +24 -0
  24. package/dist/src/config.js +17 -0
  25. package/dist/src/memoryGraph.js +299 -0
  26. package/dist/src/monitoring.js +13 -0
  27. package/dist/src/queryExpansion.js +163 -0
  28. package/dist/src/queryReformulation/cache.js +123 -0
  29. package/dist/src/queryReformulation/rrf.js +29 -0
  30. package/dist/src/queryReformulation/tfidf.js +61 -0
  31. package/dist/src/queryReformulation/vagueness.js +33 -0
  32. package/dist/src/queryReformulation.js +225 -0
  33. package/dist/src/recall.js +136 -1
  34. package/dist/src/recallMetrics.js +258 -0
  35. package/dist/src/store/sqlite/cache-stripes.js +133 -0
  36. package/dist/src/store/sqlite/fts5-search.js +68 -0
  37. package/dist/src/store/sqlite/perf-samples.js +1 -0
  38. package/dist/src/store/sqlite/schema.js +60 -1
  39. package/dist/src/store/sqlite.js +1 -0
  40. package/dist/src/store/vectorIndex.js +29 -5
  41. package/dist/src/tiered-router/types.js +18 -0
  42. package/dist/src/tieredRouter.js +421 -0
  43. package/dist/src/vectorStore.js +8 -6
  44. package/extensions/dashboard-client/dist/assets/{AchievementTiles-BoL7qBOJ.js → AchievementTiles-DR9cLGDL.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{AchievementTiles-BoL7qBOJ.js.map → AchievementTiles-DR9cLGDL.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{AchievementsTab-BFDE3HYc.js → AchievementsTab-svNUgJ1e.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{AchievementsTab-BFDE3HYc.js.map → AchievementsTab-svNUgJ1e.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +2 -0
  49. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +1 -0
  50. package/extensions/dashboard-client/dist/assets/{ConfigTab-DDFE-ckA.js → ConfigTab-CmmWheuW.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{ConfigTab-DDFE-ckA.js.map → ConfigTab-CmmWheuW.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{EventsTab-B6UxZ7PM.js → EventsTab-CFE7jLsa.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{EventsTab-B6UxZ7PM.js.map → EventsTab-CFE7jLsa.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/GameTab-CJAX_th_.js +2 -0
  55. package/extensions/dashboard-client/dist/assets/{GameTab-DehUX-KE.js.map → GameTab-CJAX_th_.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-0GWRt4wn.js → MaintenanceTab-P4SUsvMf.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-0GWRt4wn.js.map → MaintenanceTab-P4SUsvMf.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +2 -0
  59. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +1 -0
  60. package/extensions/dashboard-client/dist/assets/{MetricsTab-C3rwMVsN.js → MetricsTab-CHQscixy.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{MetricsTab-C3rwMVsN.js.map → MetricsTab-CHQscixy.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{OverviewTab-39O9_x5j.js → OverviewTab-DBs4DJdd.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{OverviewTab-39O9_x5j.js.map → OverviewTab-DBs4DJdd.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{ReposTab-Cfj6Q5sH.js → ReposTab-DeFz76L5.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{ReposTab-Cfj6Q5sH.js.map → ReposTab-DeFz76L5.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXDTCi3r.js → SessionsTab-BWNsj1OB.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXDTCi3r.js.map → SessionsTab-BWNsj1OB.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js +2 -0
  69. package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js.map +1 -0
  70. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Cw1dJ4e9.js → TimeSavedCard-CiqCOQuL.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Cw1dJ4e9.js.map → TimeSavedCard-CiqCOQuL.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{TopicsTab-BAw972BV.js → TopicsTab-BWWQxltv.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{TopicsTab-BAw972BV.js.map → TopicsTab-BWWQxltv.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/{TurnsTab-Cf5mGIdl.js → TurnsTab-CVz4f4-o.js} +2 -2
  75. package/extensions/dashboard-client/dist/assets/{TurnsTab-Cf5mGIdl.js.map → TurnsTab-CVz4f4-o.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +1 -0
  77. package/extensions/dashboard-client/dist/assets/{index-4gQ7auad.js → index-DrktNOoT.js} +11 -11
  78. package/extensions/dashboard-client/dist/assets/index-DrktNOoT.js.map +1 -0
  79. package/extensions/dashboard-client/dist/assets/{useSSE-DNgMuyP6.js → useSSE-Bf5LOLSi.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{useSSE-DNgMuyP6.js.map → useSSE-Bf5LOLSi.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/index.html +2 -2
  82. package/extensions/dashboard-client/src/App.tsx +24 -8
  83. package/extensions/dashboard-client/src/api/client.ts +15 -0
  84. package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +100 -0
  85. package/extensions/dashboard-client/src/components/StripeDistributionCard.tsx +110 -0
  86. package/extensions/dashboard-client/src/components/TabBar.tsx +49 -4
  87. package/extensions/dashboard-client/src/main.tsx +1 -0
  88. package/extensions/dashboard-client/src/styles/base.css +30 -0
  89. package/extensions/dashboard-client/src/styles/cache.css +167 -0
  90. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +75 -13
  91. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +440 -0
  92. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +65 -2
  93. package/extensions/dashboard-server/api-contracts/cache-stripes.ts +49 -0
  94. package/extensions/dashboard-server/api-contracts/endpoints.ts +31 -1
  95. package/extensions/dashboard-server/api-contracts/index.ts +16 -1
  96. package/extensions/dashboard-server/api-contracts/memory-map.ts +50 -0
  97. package/extensions/dashboard-server/api-contracts/setup.ts +22 -0
  98. package/extensions/dashboard-server/routes-cache.ts +36 -1
  99. package/extensions/dashboard-server/routes-game.ts +3 -0
  100. package/extensions/dashboard-server/routes-memory-map.ts +105 -0
  101. package/extensions/dashboard-server/routes-setup.ts +112 -0
  102. package/extensions/dashboard-server/routes.ts +3 -1
  103. package/extensions/dashboard-server/server.ts +6 -0
  104. package/extensions/mega-compact.ts +5 -0
  105. package/extensions/mega-config.ts +17 -0
  106. package/extensions/mega-events/context-handler.ts +10 -2
  107. package/extensions/mega-events/perf-handler.ts +111 -1
  108. package/extensions/mega-events/separated-prompt.ts +329 -0
  109. package/extensions/mega-runtime/env-loader.ts +55 -0
  110. package/extensions/mega-runtime/helpers.ts +2 -0
  111. package/extensions/mega-runtime/reset-runtime.ts +1 -0
  112. package/extensions/mega-runtime/runtime-snapshot.ts +1 -0
  113. package/extensions/mega-runtime/runtime.ts +1 -0
  114. package/extensions/mega-runtime/snapshot.ts +6 -0
  115. package/extensions/mega-runtime/widget-types.ts +3 -0
  116. package/extensions/mega-runtime/widget.ts +5 -0
  117. package/package.json +1 -1
  118. package/src/cache-stripe-impl.ts +385 -0
  119. package/src/cache-stripe.ts +18 -0
  120. package/src/config/stopwords.ts +25 -0
  121. package/src/config.ts +26 -0
  122. package/src/memoryGraph.ts +438 -0
  123. package/src/monitoring.ts +35 -0
  124. package/src/queryExpansion.ts +206 -0
  125. package/src/queryReformulation/cache.ts +169 -0
  126. package/src/queryReformulation/rrf.ts +51 -0
  127. package/src/queryReformulation/tfidf.ts +79 -0
  128. package/src/queryReformulation/vagueness.ts +38 -0
  129. package/src/queryReformulation.ts +355 -0
  130. package/src/recall.ts +168 -4
  131. package/src/recallMetrics.ts +388 -0
  132. package/src/store/sqlite/cache-stripes.ts +196 -0
  133. package/src/store/sqlite/fts5-search.ts +90 -0
  134. package/src/store/sqlite/perf-samples.ts +3 -1
  135. package/src/store/sqlite/schema.ts +60 -1
  136. package/src/store/sqlite.ts +1 -0
  137. package/src/store/vectorIndex.ts +38 -7
  138. package/src/tiered-router/types.ts +64 -0
  139. package/src/tieredRouter.ts +489 -0
  140. package/src/vectorStore.ts +9 -5
  141. package/extensions/dashboard-client/dist/assets/CacheTab-eKVc-NV1.js +0 -2
  142. package/extensions/dashboard-client/dist/assets/CacheTab-eKVc-NV1.js.map +0 -1
  143. package/extensions/dashboard-client/dist/assets/GameTab-DehUX-KE.js +0 -2
  144. package/extensions/dashboard-client/dist/assets/SetupTab-mMlfunJa.js +0 -2
  145. package/extensions/dashboard-client/dist/assets/SetupTab-mMlfunJa.js.map +0 -1
  146. package/extensions/dashboard-client/dist/assets/index-4gQ7auad.js.map +0 -1
  147. package/extensions/dashboard-client/dist/assets/index-BEdKlpAB.css +0 -1
@@ -0,0 +1,489 @@
1
+ /**
2
+ * tieredRouter.ts — S44 three-tier latency-aware recall routing (impl).
3
+ *
4
+ * Three tiers:
5
+ * L0: in-memory LRU cache (recent queries -> results).
6
+ * L1: FTS5 trigram BM25 search (fast, approximate).
7
+ * L2: PGlite HNSW vector search via vectorStore.searchAsync (accurate, slower).
8
+ *
9
+ * Tiered fallback: try L0, miss -> L1, insufficient -> L2, L2 throw -> sync call.
10
+ * Latency-budget configurable per tier. Real `Date.now()` delta instrumentation.
11
+ *
12
+ * Pi-agnostic: no pi runtime types. Uses `openStore` for L1 and optionally
13
+ * the existing VectorStore / vectorSearchAsync for L2.
14
+ */
15
+
16
+ import { createHash } from "node:crypto";
17
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
18
+ import { dirname, join } from "node:path";
19
+ import { openStore } from "./store/sqlite/utils.js";
20
+ import { fts5SearchScoped } from "./store/sqlite/fts5-search.js";
21
+ import { listCheckpoints } from "./store/sqlite.js";
22
+ import { getStateDir } from "./store.js";
23
+ import type { VectorStore, SearchHit } from "./vectorStore.js";
24
+ import { vectorSearchAsync } from "./vector-search.js";
25
+ import {
26
+ type TieredRecallOpts,
27
+ type RecallResult,
28
+ type TieredRouterMetrics,
29
+ DEFAULT_CACHE_SIZE,
30
+ DEFAULT_BUDGET_L0_MS,
31
+ DEFAULT_BUDGET_L1_MS,
32
+ DEFAULT_BUDGET_L2_MS,
33
+ DEFAULT_LOG_CADENCE,
34
+ } from "./tiered-router/types.js";
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Env-based config helpers
38
+ // ---------------------------------------------------------------------------
39
+
40
+ function envBool(name: string, def: boolean): boolean {
41
+ const v = process.env[name];
42
+ if (v === undefined) return def;
43
+ return v === "true" || v === "1";
44
+ }
45
+
46
+ /**
47
+ * Load fts5MaxBm25 from calibration.json or env override.
48
+ */
49
+ function loadFts5MaxBm25(stateDir: string): {
50
+ fts5MaxBm25: number | null;
51
+ uncalibrated: boolean;
52
+ } {
53
+ // Env override wins.
54
+ const envVal = process.env.MEGACOMPACT_FTS5_MAX_BM25;
55
+ if (envVal !== undefined) {
56
+ const n = Number(envVal);
57
+ if (Number.isFinite(n) && n >= 0) {
58
+ return { fts5MaxBm25: n, uncalibrated: false };
59
+ }
60
+ }
61
+
62
+ // Try calibration.json.
63
+ const calPath = join(stateDir, "calibration.json");
64
+ if (existsSync(calPath)) {
65
+ try {
66
+ const raw = readFileSync(calPath, "utf-8");
67
+ // PREVENT-001: null check on JSON.parse
68
+ const parsed: unknown = JSON.parse(raw);
69
+ const cal = parsed as Record<string, unknown>;
70
+ if (typeof cal.fts5MaxBm25 === "number" && cal.fts5MaxBm25 >= 0) {
71
+ return { fts5MaxBm25: cal.fts5MaxBm25, uncalibrated: false };
72
+ }
73
+ return { fts5MaxBm25: null, uncalibrated: true };
74
+ } catch {
75
+ // Malformed calibration file -- treat as uncalibrated.
76
+ return { fts5MaxBm25: null, uncalibrated: true };
77
+ }
78
+ }
79
+
80
+ return { fts5MaxBm25: null, uncalibrated: true };
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Cache helpers
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * Deterministic cache key from (sessionId, query, k).
89
+ * Uses first 12 hex chars of SHA-256 (collision probability negligible for a
90
+ * local cache -- full collisions across different queries produce identical keys
91
+ * only if the model IDs and query text collide, which is a cache-containment
92
+ * artifact, not a correctness bug).
93
+ */
94
+ function cacheKey(sessionId: string | undefined, query: string, k: number): string {
95
+ const payload = `${sessionId ?? "__cross__"}||${query}||${k}`;
96
+ return createHash("sha256").update(payload).digest("hex").slice(0, 12);
97
+ }
98
+
99
+ /** Entry in the LRU cache. */
100
+ interface CacheEntry {
101
+ key: string;
102
+ hits: SearchHit[];
103
+ tier: "L0" | "L1" | "L2";
104
+ }
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // TieredRouter
108
+ // ---------------------------------------------------------------------------
109
+
110
+ export class TieredRouter {
111
+ // ---------- LRU cache ----------
112
+ private readonly cacheSize: number;
113
+ private cacheMap: Map<string, CacheEntry> = new Map();
114
+
115
+ // ---------- Per-tier latency budgets ----------
116
+ private readonly budgetL0Ms: number;
117
+ private readonly budgetL1Ms: number;
118
+ private readonly budgetL2Ms: number;
119
+
120
+ // ---------- Calibration ----------
121
+ /** Max BM25 score achievable on this corpus (null = uncalibrated). */
122
+ readonly fts5MaxBm25: number | null;
123
+ /** True when no calibration file or env override is available. */
124
+ readonly uncalibrated: boolean;
125
+ /**
126
+ * Conservative fallback gate fraction for the L1 confidence check.
127
+ * When uncalibrated, we require L1 score >= fraction * (some heuristic ceiling)
128
+ * before skipping L2. A real calibration sets this dynamically.
129
+ */
130
+ private readonly l1ConfidenceFraction: number;
131
+
132
+ // ---------- State dir + log path ----------
133
+ private readonly stateDir: string;
134
+ private readonly logPath: string;
135
+
136
+ // ---------- Metrics ----------
137
+ private l0Hits = 0;
138
+ private l0Misses = 0;
139
+ private l1Hits = 0;
140
+ private l1Misses = 0;
141
+ private l2Hits = 0;
142
+ private l2Misses = 0;
143
+ private totalQueries = 0;
144
+ private accLatencyL0 = 0;
145
+ private accLatencyL1 = 0;
146
+ private accLatencyL2 = 0;
147
+ private logCadence: number;
148
+ /** Hashed query -> embedding cache (avoids re-embedding L1 hits for L2). */
149
+ private embeddingCache: Map<string, number[]> = new Map();
150
+
151
+ constructor(opts?: {
152
+ stateDir?: string;
153
+ cacheSize?: number;
154
+ budgetL0Ms?: number;
155
+ budgetL1Ms?: number;
156
+ budgetL2Ms?: number;
157
+ logCadence?: number;
158
+ fts5MaxBm25?: number | null;
159
+ }) {
160
+ const sd = opts?.stateDir ?? getStateDir();
161
+ this.stateDir = sd;
162
+ this.logPath = join(sd, "tiered-router-events.log");
163
+ this.cacheSize = opts?.cacheSize ?? DEFAULT_CACHE_SIZE;
164
+ this.budgetL0Ms = opts?.budgetL0Ms ?? DEFAULT_BUDGET_L0_MS;
165
+ this.budgetL1Ms = opts?.budgetL1Ms ?? DEFAULT_BUDGET_L1_MS;
166
+ this.budgetL2Ms = opts?.budgetL2Ms ?? DEFAULT_BUDGET_L2_MS;
167
+ this.logCadence = opts?.logCadence ?? DEFAULT_LOG_CADENCE;
168
+
169
+ // Calibration: explicit override > env/file > uncalibrated.
170
+ if (opts?.fts5MaxBm25 !== undefined) {
171
+ this.fts5MaxBm25 = opts.fts5MaxBm25;
172
+ this.uncalibrated = opts.fts5MaxBm25 === null;
173
+ } else {
174
+ const cal = loadFts5MaxBm25(this.stateDir);
175
+ this.fts5MaxBm25 = cal.fts5MaxBm25;
176
+ this.uncalibrated = cal.uncalibrated;
177
+ }
178
+
179
+ // Conservative fallback: when uncalibrated, use a small fraction
180
+ // (10% of an assumed ceiling 0.5) to bias toward L2 fallback.
181
+ // A real calibration enables the full confidence gate.
182
+ this.l1ConfidenceFraction = this.fts5MaxBm25 != null ? 0.7 : 0.1;
183
+ }
184
+
185
+ // -----------------------------------------------------------------------
186
+ // Public API
187
+ // -----------------------------------------------------------------------
188
+
189
+ /**
190
+ * Three-tier recall routing: L0 (cache) -> L1 (FTS5) -> L2 (PGlite vec).
191
+ *
192
+ * @param query The search query text.
193
+ * @param opts Options including sessionId, k, flag overrides.
194
+ * @param store The VectorStore instance (needed for L2 hydration).
195
+ * @returns Hits ranked by the tier that satisfied the query.
196
+ */
197
+ async route(
198
+ query: string,
199
+ opts: TieredRecallOpts,
200
+ store: VectorStore,
201
+ ): Promise<RecallResult> {
202
+ this.totalQueries++;
203
+ const k = opts.k ?? 10;
204
+ const sessionId = opts.sessionId;
205
+
206
+ // ---- L0: in-memory LRU cache ----
207
+ const ck = cacheKey(sessionId, query, k);
208
+ if (!opts.bypassCache) {
209
+ try {
210
+ const t0L0 = Date.now();
211
+ const cached = this.cacheMap.get(ck);
212
+ const latL0 = Date.now() - t0L0;
213
+ this.accLatencyL0 += latL0;
214
+
215
+ if (cached) {
216
+ this.l0Hits++;
217
+ // Promote to front (delete + set).
218
+ this.cacheMap.delete(ck);
219
+ this.cacheMap.set(ck, cached);
220
+ this.maybeLogMetrics();
221
+ if (latL0 > this.budgetL0Ms) {
222
+ this.logBudgetWarn("L0", latL0, this.budgetL0Ms);
223
+ }
224
+ return { hits: cached.hits, tier: "L0", latencyMs: { l0: latL0 } };
225
+ }
226
+ this.l0Misses++;
227
+ } catch (err) {
228
+ this.l0Misses++;
229
+ this.logTierThrew("L0", err);
230
+ }
231
+ }
232
+
233
+ // ---- L1: FTS5 trigram BM25 search ----
234
+ let fts5Hits: Array<{ id: string; score: number }> = [];
235
+ try {
236
+ const t0L1 = Date.now();
237
+ const reader = openStore(this.stateDir);
238
+ fts5Hits = fts5SearchScoped(query, reader, sessionId, k);
239
+ const latL1 = Date.now() - t0L1;
240
+ this.accLatencyL1 += latL1;
241
+ if (latL1 > this.budgetL1Ms) {
242
+ this.logBudgetWarn("L1", latL1, this.budgetL1Ms);
243
+ }
244
+ } catch (err) {
245
+ this.logTierThrew("L1", err);
246
+ // Fall through to L2.
247
+ }
248
+
249
+ const l1Score = fts5Hits.length > 0 ? fts5Hits[0]!.score : 0;
250
+ const l1HighEnough =
251
+ fts5Hits.length > 0 &&
252
+ this.fts5MaxBm25 != null &&
253
+ l1Score >= this.l1ConfidenceFraction * this.fts5MaxBm25;
254
+
255
+ if (l1HighEnough && fts5Hits.length >= k) {
256
+ this.l1Hits++;
257
+ const hits = await this.hydrateHits(fts5Hits.slice(0, k), sessionId);
258
+ this.cacheResult(ck, hits, "L1");
259
+ this.maybeLogMetrics();
260
+ return {
261
+ hits,
262
+ tier: "L1",
263
+ latencyMs: { l0: 0, l1: 0 },
264
+ };
265
+ }
266
+
267
+ if (fts5Hits.length > 0) {
268
+ this.l1Misses++; // L1 returned results but insufficient confidence
269
+ } else {
270
+ this.l1Misses++;
271
+ }
272
+
273
+ // ---- L2: PGlite HNSW vector search ----
274
+ try {
275
+ const t0L2 = Date.now();
276
+ const asyncHits = await vectorSearchAsync(store, sessionId ?? "", query, k);
277
+ const latL2 = Date.now() - t0L2;
278
+ this.accLatencyL2 += latL2;
279
+
280
+ if (asyncHits.length > 0) {
281
+ this.l2Hits++;
282
+ const hits = asyncHits.slice(0, k);
283
+ this.cacheResult(ck, hits, "L2");
284
+ this.maybeLogMetrics();
285
+ if (latL2 > this.budgetL2Ms) {
286
+ this.logBudgetWarn("L2", latL2, this.budgetL2Ms);
287
+ }
288
+ return { hits, tier: "L2", latencyMs: { l2: latL2 } };
289
+ }
290
+ this.l2Misses++;
291
+ } catch (err) {
292
+ this.l2Misses++;
293
+ this.logTierThrew("L2", err);
294
+ // Fall through -- caller should fall back to sync searchRecall().
295
+ }
296
+
297
+ this.maybeLogMetrics();
298
+ return { hits: [], tier: "L2", latencyMs: {} };
299
+ }
300
+
301
+ /**
302
+ * Synchronous L0 cache peek. Returns cached hits for (sessionId, query, k)
303
+ * and promotes the entry to the front of the LRU, or null on miss. Use this
304
+ * from synchronous recall paths that cannot await route() (route() is async).
305
+ * Does not touch L1/L2 — only the in-memory LRU.
306
+ */
307
+ peekCache(sessionId: string | undefined, query: string, k: number): SearchHit[] | null {
308
+ const ck = cacheKey(sessionId, query, k);
309
+ const cached = this.cacheMap.get(ck);
310
+ if (!cached) return null;
311
+ this.l0Hits++;
312
+ this.cacheMap.delete(ck);
313
+ this.cacheMap.set(ck, cached); // promote to front
314
+ return cached.hits;
315
+ }
316
+
317
+ /**
318
+ * Invalidate cached results for a given session.
319
+ * Called when new messages arrive (session context changed).
320
+ */
321
+ invalidateSession(sessionId?: string): void {
322
+ if (sessionId) {
323
+ const prefix = `${sessionId}||`;
324
+ for (const [key] of this.cacheMap) {
325
+ // cacheKey format: <sessionId>||<query>||<k>
326
+ if (key.startsWith(prefix)) {
327
+ this.cacheMap.delete(key);
328
+ }
329
+ }
330
+ } else {
331
+ // Cross-session entries start with "__cross__||"
332
+ for (const [key] of this.cacheMap) {
333
+ if (key.startsWith("__cross__||")) {
334
+ this.cacheMap.delete(key);
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ /** Force-clear the entire result cache. */
341
+ clearCache(): void {
342
+ this.cacheMap.clear();
343
+ this.embeddingCache.clear();
344
+ }
345
+
346
+ /** Snapshot of current metrics for dashboard / logging. */
347
+ getMetrics(): TieredRouterMetrics {
348
+ const q = Math.max(this.totalQueries, 1);
349
+ return {
350
+ l0Hits: this.l0Hits,
351
+ l0Misses: this.l0Misses,
352
+ l1Hits: this.l1Hits,
353
+ l1Misses: this.l1Misses,
354
+ l2Hits: this.l2Hits,
355
+ l2Misses: this.l2Misses,
356
+ totalQueries: this.totalQueries,
357
+ avgLatencyMs: {
358
+ l0: Math.round((this.accLatencyL0 / q) * 100) / 100,
359
+ l1: Math.round((this.accLatencyL1 / q) * 100) / 100,
360
+ l2: Math.round((this.accLatencyL2 / q) * 100) / 100,
361
+ },
362
+ fts5MaxBm25: this.fts5MaxBm25,
363
+ uncalibrated: this.uncalibrated,
364
+ };
365
+ }
366
+
367
+ /** Reset all metrics counters (for testing). */
368
+ resetMetrics(): void {
369
+ this.l0Hits = 0;
370
+ this.l0Misses = 0;
371
+ this.l1Hits = 0;
372
+ this.l1Misses = 0;
373
+ this.l2Hits = 0;
374
+ this.l2Misses = 0;
375
+ this.totalQueries = 0;
376
+ this.accLatencyL0 = 0;
377
+ this.accLatencyL1 = 0;
378
+ this.accLatencyL2 = 0;
379
+ }
380
+
381
+ // -----------------------------------------------------------------------
382
+ // Internal helpers
383
+ // -----------------------------------------------------------------------
384
+
385
+ /**
386
+ * Hydrate FTS5 hits (which only carry checkpoint `id` + `score`) into
387
+ * full SearchHit objects by fetching from the sync store.
388
+ */
389
+ private async hydrateHits(
390
+ fts5Hits: Array<{ id: string; score: number }>,
391
+ sessionId?: string,
392
+ ): Promise<SearchHit[]> {
393
+ const cps = listCheckpoints(sessionId ?? "", this.stateDir);
394
+ const cpMap = new Map(cps.map((cp) => [cp.checkpointId, cp]));
395
+ const hits: SearchHit[] = [];
396
+ for (const fh of fts5Hits) {
397
+ const cp = cpMap.get(fh.id);
398
+ if (cp) {
399
+ hits.push({ checkpoint: cp, score: fh.score });
400
+ }
401
+ }
402
+ return hits;
403
+ }
404
+
405
+ /** Insert into the LRU cache, evicting oldest entry if over capacity. */
406
+ private cacheResult(key: string, hits: SearchHit[], tier: "L0" | "L1" | "L2"): void {
407
+ if (this.cacheMap.has(key)) {
408
+ this.cacheMap.delete(key);
409
+ } else if (this.cacheMap.size >= this.cacheSize) {
410
+ const oldest = this.cacheMap.keys().next();
411
+ if (oldest.value !== undefined) {
412
+ this.cacheMap.delete(oldest.value);
413
+ }
414
+ }
415
+ this.cacheMap.set(key, { key, hits, tier });
416
+ }
417
+
418
+ /** Append a structured event to the tiered-router events log (best-effort). */
419
+ private appendLog(ev: Record<string, unknown>): void {
420
+ try {
421
+ mkdirSync(dirname(this.logPath), { recursive: true });
422
+ appendFileSync(this.logPath, JSON.stringify(ev) + "\n");
423
+ } catch {
424
+ /* best-effort -- never break extension on log failure */
425
+ }
426
+ }
427
+
428
+ /** Log periodic tiered metrics at the configured cadence. */
429
+ private maybeLogMetrics(): void {
430
+ if (this.totalQueries % this.logCadence !== 0) return;
431
+ const m = this.getMetrics();
432
+ const payload: Record<string, unknown> = {
433
+ event: "tiered_routing_metrics",
434
+ l0Hits: m.l0Hits,
435
+ l0Misses: m.l0Misses,
436
+ l1Hits: m.l1Hits,
437
+ l1Misses: m.l1Misses,
438
+ l2Hits: m.l2Hits,
439
+ l2Misses: m.l2Misses,
440
+ avgLatencyMs: m.avgLatencyMs,
441
+ totalQueries: m.totalQueries,
442
+ fts5MaxBm25: m.fts5MaxBm25,
443
+ uncalibrated: m.uncalibrated,
444
+ };
445
+ if (m.uncalibrated) {
446
+ (payload as Record<string, string>).note = "run scripts/calibrate-fts5.mjs";
447
+ }
448
+ this.appendLog(payload);
449
+ }
450
+
451
+ /** Log a tier-threw event (non-fatal fall-through). */
452
+ private logTierThrew(tier: string, err: unknown): void {
453
+ const message = err instanceof Error ? err.message : String(err);
454
+ this.appendLog({ event: "tier_threw", tier, error: message });
455
+ }
456
+
457
+ /** Log a budget-warning event. */
458
+ private logBudgetWarn(tier: string, actualMs: number, budgetMs: number): void {
459
+ this.appendLog({ event: "tier_budget", tier, actualMs, budgetMs });
460
+ }
461
+ }
462
+
463
+ // ---------------------------------------------------------------------------
464
+ // Factory / singleton
465
+ // ---------------------------------------------------------------------------
466
+
467
+ let _defaultRouter: TieredRouter | null = null;
468
+
469
+ /**
470
+ * Get or create the singleton tiered router.
471
+ * Feature-gated on TIERED_ROUTING_ENABLED (default true).
472
+ * Returns null when the flag is OFF, so callers can fall through cleanly.
473
+ */
474
+ export function getTieredRouter(): TieredRouter | null {
475
+ const enabled = envBool("MEGACOMPACT_TIERED_ROUTING_ENABLED", true);
476
+ if (!enabled) return null;
477
+
478
+ if (!_defaultRouter) {
479
+ _defaultRouter = new TieredRouter({
480
+ stateDir: getStateDir(),
481
+ });
482
+ }
483
+ return _defaultRouter;
484
+ }
485
+
486
+ /** Reset the singleton (for testing). */
487
+ export function resetTieredRouter(): void {
488
+ _defaultRouter = null;
489
+ }
@@ -176,6 +176,8 @@ export class VectorStore {
176
176
  result: "deduped" | "new" | "mark_only",
177
177
  reason: string | undefined,
178
178
  latencyMs: number,
179
+ similarityScore?: number,
180
+ matchedId?: string,
179
181
  ): void {
180
182
  if (!this.eventsPath) return;
181
183
  logDecision(this.eventsPath, {
@@ -184,6 +186,8 @@ export class VectorStore {
184
186
  result,
185
187
  reason,
186
188
  latencyMs: Math.round(latencyMs * 100) / 100,
189
+ similarityScore,
190
+ matchedId,
187
191
  });
188
192
  }
189
193
 
@@ -241,7 +245,7 @@ export class VectorStore {
241
245
  deduped: true,
242
246
  reason: "contentHash",
243
247
  };
244
- this.record("L0", "deduped", "contentHash", Date.now() - t0);
248
+ this.record("L0", "deduped", "contentHash", Date.now() - t0, 1, contentMatch.checkpointId);
245
249
  onTier?.({ tier: "L0", status: "deduped", detail: "contentHash" });
246
250
  return r;
247
251
  }
@@ -263,7 +267,7 @@ export class VectorStore {
263
267
  deduped: true,
264
268
  reason: "regionHash",
265
269
  };
266
- this.record("L0", "deduped", "regionHash", Date.now() - t0);
270
+ this.record("L0", "deduped", "regionHash", Date.now() - t0, 1, regionMatch.checkpointId);
267
271
  onTier?.({ tier: "L0", status: "deduped", detail: "regionHash" });
268
272
  return r;
269
273
  }
@@ -291,7 +295,7 @@ export class VectorStore {
291
295
  deduped: true,
292
296
  reason: "summaryHash",
293
297
  };
294
- this.record("L0", "deduped", "summaryHash", Date.now() - t0);
298
+ this.record("L0", "deduped", "summaryHash", Date.now() - t0, 1, summaryMatch.checkpointId);
295
299
  onTier?.({ tier: "L0", status: "deduped", detail: "summaryHash" });
296
300
  return r;
297
301
  }
@@ -312,7 +316,7 @@ export class VectorStore {
312
316
  upsertCheckpoint(l1, this.stateDir);
313
317
  bumpDedupStats(true, this.stateDir);
314
318
  const r = { checkpoint: l1, deduped: true, reason: "l1MinHash" };
315
- this.record("L1", "deduped", "l1MinHash", Date.now() - t0);
319
+ this.record("L1", "deduped", "l1MinHash", Date.now() - t0, 1, l1.checkpointId);
316
320
  onTier?.({ tier: "L1", status: "deduped", detail: "l1MinHash" });
317
321
  return r;
318
322
  }
@@ -357,7 +361,7 @@ export class VectorStore {
357
361
  deduped: true,
358
362
  reason: "contentSimilarity",
359
363
  };
360
- this.record("L2", "deduped", "contentSimilarity", Date.now() - t0);
364
+ this.record("L2", "deduped", "contentSimilarity", Date.now() - t0, nearest.sim, nearest.checkpoint.checkpointId);
361
365
  onTier?.({
362
366
  tier: "L2",
363
367
  status: "deduped",
@@ -1,2 +0,0 @@
1
- import{j as a,u as v,r as m,i as b,c as j}from"./index-4gQ7auad.js";import{C,T as S}from"./TimeSavedCard-Cw1dJ4e9.js";import{b as n,c as o,d as g,e as T}from"./format-COjDIVGj.js";import{l as f}from"./pricing-CvE4NllQ.js";function x(e){return e>=80?"ov-stat-value-green":e>=50?"ov-stat-value-yellow":"ov-stat-value-red"}function p(e){return e==null?"":e>=0?"ov-stat-value-green":"ov-stat-value-red"}function t({label:e,value:l,className:s,title:c}){return a.jsxs("div",{className:"ov-stat-row",children:[a.jsx("span",{className:"ov-stat-label",title:c,children:e}),a.jsx("span",{className:`ov-stat-value${s?` ${s}`:""}`,children:l})]})}function R(e){return a.jsxs("div",{className:"card provider-cache-card",children:[a.jsx(t,{label:"Cache Hit Rate",value:`${e.hitPct.toFixed(1)}%`,className:x(e.hitPct)}),a.jsx(t,{label:"Turns Recorded",value:String(e.turnCount)}),a.jsx(t,{label:"Cache Read Tokens",value:n(e.totalCacheRead)}),a.jsx(t,{label:"Cache Write Tokens",value:n(e.totalCacheWrite)}),a.jsx(t,{label:"Input Tokens",value:n(e.totalInput)}),a.jsx(t,{label:"$ Saved (reads)",value:o(e.cacheReadSaved)}),a.jsx(t,{label:"Write Investment",value:o(e.cacheWriteCost)}),a.jsx(t,{label:"Net Saved",value:o(e.netSaved),className:p(e.netSaved)}),a.jsx(t,{label:"Model",value:e.modelLabel??"—"}),a.jsx(t,{label:"Tracked Since",value:g(e.firstTurnAt)}),a.jsx(t,{label:"Last Updated",value:T(e.latestTurnAt)}),e.byModel.length>0&&a.jsxs(a.Fragment,{children:[a.jsx("div",{className:"ov-stat-section-label",children:"Per Model"}),e.byModel.map(l=>{const s=f(l.model),c=s!=null?l.totalCacheRead/1e6*s:null,r=s!=null?l.totalCacheWrite/1e6*s:null;return a.jsxs("div",{className:"ov-stat-group",children:[a.jsx(t,{label:"Model",value:l.model}),a.jsx(t,{label:"Hit Rate",value:`${l.hitPct.toFixed(1)}%`,className:x(l.hitPct)}),a.jsx(t,{label:"Cache Read",value:n(l.totalCacheRead)}),a.jsx(t,{label:"Cache Write",value:n(l.totalCacheWrite)}),a.jsx(t,{label:"Samples",value:String(l.sampleCount)}),a.jsx(t,{label:"$ Saved (reads)",value:c!=null?o(c):"—"}),a.jsx(t,{label:"Write Investment",value:r!=null?o(r):"—"})]},l.model)})]})]})}function H(){const{data:e,loading:l,error:s}=v(m.useCallback(()=>b(),[]),{pollInterval:5e3}),{data:c,loading:r,error:u}=v(m.useCallback(()=>j(),[]),{pollInterval:5e3});return a.jsxs("div",{className:"cache-tab",children:[a.jsx("h2",{children:"Provider Prompt Cache"}),l?a.jsx("p",{className:"tab-stub",children:"Loading…"}):s?a.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",s.message]}):e?a.jsx(R,{hitPct:e.cache.avgHitPct,turnCount:e.cache.turnCount,totalCacheRead:e.cache.totalCacheRead,totalCacheWrite:e.cache.totalCacheWrite,totalInput:e.cache.totalInput,cacheReadSaved:e.savings?.cacheReadSaved??null,cacheWriteCost:e.savings?.cacheWriteCost??null,netSaved:e.savings?.netSaved??null,modelLabel:e.savings?.model??null,firstTurnAt:e.cache.firstTurnAt,latestTurnAt:e.cache.latestTurnAt,byModel:e.cache.byModel??[]}):null,a.jsx("h2",{children:"Mega-Compact Dedup Cache"}),r?a.jsx("p",{className:"tab-stub",children:"Loading…"}):u?a.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",u.message]}):c&&(()=>{const{cacheHits:i,compacts:h,timeSaved:d}=c;return a.jsxs("div",{className:"card-grid overview-card-grid",children:[a.jsx(C,{cacheHitsSession:i.session,cacheHitsTotal:i.total,tokensSavedSession:i.sessionTokensSaved,tokensSavedTotal:i.totalTokensSaved,compactionsSession:h.session,compactionsTotal:h.total}),a.jsx(S,{compactSessionSec:d.compact.sessionSec,compactTotalSec:d.compact.totalSec,cacheHitSessionSec:d.cacheHit.sessionSec,cacheHitTotalSec:d.cacheHit.totalSec})]})})()]})}export{H as default};
2
- //# sourceMappingURL=CacheTab-eKVc-NV1.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CacheTab-eKVc-NV1.js","sources":["../../src/components/ProviderCacheCard.tsx","../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/ProviderCacheCard.tsx — Provider prompt cache stats card.\n *\n * Displays lifetime provider prompt cache hit-rate aggregates + estimated\n * dollar savings from the /api/provider-cache endpoint.\n * Reuses the `ov-stat-row` / `StatRow` pattern from CacheHitsCard.tsx.\n *\n * B.2 spec: Cache Hit Rate with color classes, humanized token counts,\n * priced $ rows with green/red net, model name, locale date + relative time.\n */\n\nimport type React from \"react\";\nimport {\n\tfmtTokens,\n\tfmtDollars,\n\tfmtDate,\n\tfmtRelativeTime,\n} from \"../utils/format\";\nimport { lookupModelInputRate } from \"@pricing\";\n\n// ---------------------------------------------------------------------------\n// Per-model breakdown type (mirrors the API contract).\n// ---------------------------------------------------------------------------\n\nexport interface ByModelEntry {\n\tmodel: string;\n\thitPct: number;\n\ttotalCacheRead: number;\n\ttotalCacheWrite: number;\n\tsampleCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Flattened props — destructured from ProviderCacheResponse in CacheTab.\n// ---------------------------------------------------------------------------\n\nexport interface ProviderCacheCardProps {\n\t/** Cache hit rate percentage (0–100). */\n\thitPct: number;\n\t/** Number of turns with cache_hit_pct samples. */\n\tturnCount: number;\n\t/** Total cache-read tokens across all sampled turns. */\n\ttotalCacheRead: number;\n\t/** Total cache-write tokens across all sampled turns. */\n\ttotalCacheWrite: number;\n\t/** Total input tokens across all sampled turns. */\n\ttotalInput: number;\n\t/** $ saved from cache reads (null when unpriced). */\n\tcacheReadSaved: number | null;\n\t/** $ spent on cache writes (null when unpriced). */\n\tcacheWriteCost: number | null;\n\t/** netSaved = cacheReadSaved - cacheWriteCost (null when unpriced). */\n\tnetSaved: number | null;\n\t/** Model display name (modelName || modelId, or null). */\n\tmodelLabel: string | null;\n\t/** ISO timestamp of first recorded turn. */\n\tfirstTurnAt: string | null;\n\t/** ISO timestamp of latest recorded turn. */\n\tlatestTurnAt: string | null;\n\t/** Per-model breakdown (F4). */\n\tbyModel: ByModelEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Hit-rate color: green ≥80, yellow ≥50, red <50. */\nfunction hitPctClass(pct: number): string {\n\tif (pct >= 80) return \"ov-stat-value-green\";\n\tif (pct >= 50) return \"ov-stat-value-yellow\";\n\treturn \"ov-stat-value-red\";\n}\n\n/** Net-saved color: green positive, red negative. */\nfunction netClass(v: number | null): string {\n\tif (v == null) return \"\";\n\treturn v >= 0 ? \"ov-stat-value-green\" : \"ov-stat-value-red\";\n}\n\nfunction StatRow({\n\tlabel,\n\tvalue,\n\tclassName,\n\ttitle,\n}: {\n\tlabel: string;\n\tvalue: string;\n\tclassName?: string;\n\ttitle?: string;\n}): React.ReactElement {\n\treturn (\n\t\t<div className=\"ov-stat-row\">\n\t\t\t<span className=\"ov-stat-label\" title={title}>\n\t\t\t\t{label}\n\t\t\t</span>\n\t\t\t<span className={`ov-stat-value${className ? ` ${className}` : \"\"}`}>\n\t\t\t\t{value}\n\t\t\t</span>\n\t\t</div>\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\nexport function ProviderCacheCard(\n\tprops: ProviderCacheCardProps,\n): React.ReactElement {\n\treturn (\n\t\t<div className=\"card provider-cache-card\">\n\t\t\t{/* --- Hit Rate + Turns --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Hit Rate\"\n\t\t\t\tvalue={`${props.hitPct.toFixed(1)}%`}\n\t\t\t\tclassName={hitPctClass(props.hitPct)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Turns Recorded\" value={String(props.turnCount)} />\n\n\t\t\t{/* --- Token counts --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Read Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheRead)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Write Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheWrite)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Input Tokens\" value={fmtTokens(props.totalInput)} />\n\n\t\t\t{/* --- Dollar rows (null when unpriced) --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\tvalue={fmtDollars(props.cacheReadSaved)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\tvalue={fmtDollars(props.cacheWriteCost)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Net Saved\"\n\t\t\t\tvalue={fmtDollars(props.netSaved)}\n\t\t\t\tclassName={netClass(props.netSaved)}\n\t\t\t/>\n\n\t\t\t{/* --- Model + timestamps --- */}\n\t\t\t<StatRow label=\"Model\" value={props.modelLabel ?? \"\\u2014\"} />\n\t\t\t<StatRow label=\"Tracked Since\" value={fmtDate(props.firstTurnAt)} />\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Last Updated\"\n\t\t\t\tvalue={fmtRelativeTime(props.latestTurnAt)}\n\t\t\t/>\n\n\t\t\t{/* --- Per-model breakdown (F4) --- */}\n\t\t\t{props.byModel.length > 0 && (\n\t\t\t\t<>\n\t\t\t\t\t<div className=\"ov-stat-section-label\">Per Model</div>\n\t\t\t\t\t{props.byModel.map((m) => {\n\t\t\t\t\t\tconst rate = lookupModelInputRate(m.model);\n\t\t\t\t\t\tconst cr =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheRead / 1_000_000) * rate : null;\n\t\t\t\t\t\tconst cw =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheWrite / 1_000_000) * rate : null;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={m.model} className=\"ov-stat-group\">\n\t\t\t\t\t\t\t\t<StatRow label=\"Model\" value={m.model} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Hit Rate\"\n\t\t\t\t\t\t\t\t\tvalue={`${m.hitPct.toFixed(1)}%`}\n\t\t\t\t\t\t\t\t\tclassName={hitPctClass(m.hitPct)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Read\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheRead)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Write\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheWrite)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow label=\"Samples\" value={String(m.sampleCount)} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\t\t\t\t\t\tvalue={cr != null ? fmtDollars(cr) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\t\t\t\t\t\tvalue={cw != null ? fmtDollars(cw) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/CacheTab.tsx — Cache tab (B.3).\n *\n * Two sections, each with its own header:\n * 1. Provider Prompt Cache → ProviderCacheCard (/api/provider-cache)\n * 2. Mega-Compact Dedup Cache → CacheHitsCard + TimeSavedCard (/api/snapshot)\n *\n * Both fetch at 5s polling (same interval). Loading / error shown per-section\n * only when its own fetch fails; the other section keeps rendering.\n */\n\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport type { SnapshotResponse, ProviderCacheResponse } from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchSnapshot, fetchProviderCache } from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\nimport { ProviderCacheCard } from \"../components/ProviderCacheCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\t/* --- Provider prompt cache (5s poll) --- */\n\tconst {\n\t\tdata: providerCache,\n\t\tloading: pcLoading,\n\t\terror: pcError,\n\t} = useApi<ProviderCacheResponse>(\n\t\tuseCallback(() => fetchProviderCache(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\t/* --- Dedup cache snapshot (5s poll) --- */\n\tconst {\n\t\tdata: snapshot,\n\t\tloading: snapLoading,\n\t\terror: snapError,\n\t} = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t{/* ==============================================================\n\t\t\t Section 1 — Provider Prompt Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Provider Prompt Cache</h2>\n\t\t\t{pcLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading…</p>\n\t\t\t) : pcError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tProvider cache unavailable: {pcError.message}\n\t\t\t\t</p>\n\t\t\t) : providerCache ? (\n\t\t\t\t<ProviderCacheCard\n\t\t\t\t\thitPct={providerCache.cache.avgHitPct}\n\t\t\t\t\tturnCount={providerCache.cache.turnCount}\n\t\t\t\t\ttotalCacheRead={providerCache.cache.totalCacheRead}\n\t\t\t\t\ttotalCacheWrite={providerCache.cache.totalCacheWrite}\n\t\t\t\t\ttotalInput={providerCache.cache.totalInput}\n\t\t\t\t\tcacheReadSaved={providerCache.savings?.cacheReadSaved ?? null}\n\t\t\t\t\tcacheWriteCost={providerCache.savings?.cacheWriteCost ?? null}\n\t\t\t\t\tnetSaved={providerCache.savings?.netSaved ?? null}\n\t\t\t\t\tmodelLabel={providerCache.savings?.model ?? null}\n\t\t\t\t\tfirstTurnAt={providerCache.cache.firstTurnAt}\n\t\t\t\t\tlatestTurnAt={providerCache.cache.latestTurnAt}\n\t\t\t\t\t\t\t\t\tbyModel={providerCache.cache.byModel ?? []}\n\t\t\t\t/>\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 2 — Mega-Compact Dedup Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Mega-Compact Dedup Cache</h2>\n\t\t\t{snapLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading…</p>\n\t\t\t) : snapError ? (\n\t\t\t\t<p className=\"tab-stub\">Snapshot unavailable: {snapError.message}</p>\n\t\t\t) : (\n\t\t\t\tsnapshot &&\n\t\t\t\t(() => {\n\t\t\t\t\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t\t\t\t<CacheHitsCard\n\t\t\t\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<TimeSavedCard\n\t\t\t\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})()\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["hitPctClass","pct","netClass","v","StatRow","label","value","className","title","jsxs","jsx","ProviderCacheCard","props","fmtTokens","fmtDollars","fmtDate","fmtRelativeTime","Fragment","m","rate","lookupModelInputRate","cr","cw","CacheTab","providerCache","pcLoading","pcError","useApi","useCallback","fetchProviderCache","snapshot","snapLoading","snapError","fetchSnapshot","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{r as n,k as m,l as H,j as e,d as I}from"./index-4gQ7auad.js";import{A as $}from"./AchievementTiles-BoL7qBOJ.js";function g(s){return(Math.round(s*10)/10).toString()+"%"}function b(s){return s!=null?new Date(s).toLocaleString():"—"}function M(s){return s&&typeof s=="object"?s:{}}function F(s){return s.split("/").pop()||s}function p({title:s,rows:u,badge:i,renderValue:v}){return e.jsxs("div",{className:"lb-card",children:[e.jsxs("h3",{children:[s,i]}),u.length===0?e.jsx("div",{className:"lb-empty",children:"—"}):e.jsx("table",{children:e.jsx("tbody",{children:u.map((o,x)=>e.jsxs("tr",{children:[e.jsx("td",{title:o.repo_root,children:F(o.repo_root)}),e.jsx("td",{className:"num",children:v(o)})]},`${o.repo_root}-${x}`))})})]})}function B(){const[s,u]=n.useState({}),[i,v]=n.useState(0),[o,x]=n.useState([]),[T,R]=n.useState(!1),[_,G]=n.useState(!0),[w,N]=n.useState(null),j=n.useRef(0),y=n.useCallback(async()=>{try{const[t,a,c,l,k,D]=await Promise.all([m({metric:"cache",limit:25}),m({metric:"dedupe",limit:25}),m({metric:"turns",limit:25}),m({metric:"mega_cache",limit:25}),m({metric:"repos",limit:1}),H()]),E=l.find(f=>f.ts>j.current&&f.value>100);j.current>0&&E&&(N(`oopsie! cache went to ${Math.round(E.value)}% — MEGA CACHE 🥧`),setTimeout(()=>N(null),4e3)),l.length>0&&(j.current=Math.max(...l.map(f=>f.ts))),u({cache:t,dedupe:a,turns:c,mega_cache:l}),v(k[0]?.value??0),x(D)}catch{}finally{G(!1)}},[]);n.useEffect(()=>{let t=!0;const a=async()=>{t&&await y()};a();const c=setInterval(()=>void a(),15e3);return()=>{t=!1,clearInterval(c)}},[y]),n.useEffect(()=>{let t=!0;const a=async()=>{try{const l=await I();t&&(R(!!l.game_mode_on),l.theme&&(document.documentElement.dataset.theme=l.theme))}catch{}};a();const c=setInterval(()=>void a(),15e3);return()=>{t=!1,clearInterval(c)}},[]);const S=s.mega_cache??[];let r=null,d=null;for(const t of S){(r==null||t.value>r)&&(r=t.value);const a=M(t.meta),c=a.firstSeenTs??a.firstSeen??t.ts;(d==null||c<d)&&(d=c)}const A=r!=null&&r>100,h=s.turns??[],L=h.reduce((t,a)=>Math.max(t,a.value),0),V=Math.floor(Math.log2(L+1))+1,C=(s.cache?.length??0)>0||(s.dedupe?.length??0)>0||h.length>0||S.length>0||i>0;return e.jsxs("div",{className:`game-tab${T?" game-mode-on":""}`,children:[w!=null&&e.jsx("div",{className:"mega-cache-toast show",children:w}),A&&e.jsxs("div",{className:"mega-cache-banner",children:["🥧"," MEGA CACHE! peak ",g(r)," — first reached ",b(d)]}),A&&e.jsxs("div",{className:"achievement-tile unlocked",children:["🏆"," Opie's Wild Ride",e.jsxs("span",{className:"ach-detail",children:["best ",g(r)," · first ",b(d)]})]}),e.jsx("h2",{children:"High Scores"}),!C&&!_&&e.jsx("div",{className:"game-empty",children:"No scores yet — run a session with game mode on."}),C&&e.jsxs("div",{className:"game-leaderboards",children:[e.jsx(p,{title:"Cache %",rows:s.cache??[],renderValue:t=>g(t.value)}),e.jsx(p,{title:"Dedupe (collapsed)",rows:s.dedupe??[],renderValue:t=>t.value.toLocaleString()}),e.jsx(p,{title:"Turns",rows:h,badge:h.length>0?e.jsxs("span",{className:"lvl-badge",children:["LVL ",V]}):void 0,renderValue:t=>t.value.toLocaleString()}),e.jsx(p,{title:"MEGA CACHE trophies",rows:S,renderValue:t=>e.jsxs(e.Fragment,{children:[g(t.value),e.jsx("span",{className:"lb-meta",children:b(M(t.meta).firstSeenTs??M(t.meta).firstSeen??t.ts)})]})})]}),i>0&&e.jsx("div",{className:"repos-badge-line",children:e.jsxs("span",{className:"repos-badge",children:[i," repos"]})}),e.jsx("h3",{children:"Achievements"}),e.jsx($,{achievements:o})]})}export{B as default};
2
- //# sourceMappingURL=GameTab-DehUX-KE.js.map
@@ -1,2 +0,0 @@
1
- import{r as l,e as S,g as E,j as e}from"./index-4gQ7auad.js";const t={container:{padding:"1rem",maxWidth:"720px",margin:"0 auto",fontFamily:"system-ui, sans-serif",color:"#e0e0e0"},section:{background:"#1a1a2e",borderRadius:"8px",padding:"1rem",marginBottom:"1rem",border:"1px solid #2a2a4e"},sectionTitle:{fontSize:"1.1rem",fontWeight:700,marginTop:0,marginBottom:"0.75rem",color:"#e0e0e0",borderBottom:"1px solid #333",paddingBottom:"0.5rem"},label:{fontWeight:600,color:"#a0a0c0",marginRight:"0.5rem"},value:{color:"#e0e0e0"},row:{marginBottom:"0.4rem",fontSize:"0.9rem"},warning:{background:"#3a2a00",border:"1px solid #665500",borderRadius:"8px",padding:"0.75rem",marginTop:"0.5rem",color:"#ffcc00",fontSize:"0.85rem"},badge:{display:"inline-block",padding:"0.15rem 0.5rem",borderRadius:"4px",fontSize:"0.8rem",fontWeight:600,marginLeft:"0.5rem"},detectRow:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"0.5rem 0",borderBottom:"1px solid #2a2a4e"},button:{background:"#3a5a9f",color:"#fff",border:"none",borderRadius:"6px",padding:"0.5rem 1rem",cursor:"pointer",fontSize:"0.9rem",fontWeight:600}};function w(){const[s,p]=l.useState(null),[n,u]=l.useState(null),[i,x]=l.useState(null),[c,m]=l.useState(null),[h,o]=l.useState(!1),a=l.useCallback(()=>{S().then(p).catch(r=>x(r instanceof Error?r.message:String(r)))},[]),g=l.useCallback(()=>{o(!0),m(null),E().then(r=>{u(r),o(!1)}).catch(r=>{m(r instanceof Error?r.message:String(r)),o(!1)})},[]);l.useEffect(()=>{a()},[a]),l.useEffect(()=>{const r=setInterval(a,3e4);return()=>clearInterval(r)},[a]);const f=r=>{switch(r){case"trigram":return"TrigramEmbedder (heuristic, default)";case"http":return"HTTP Embedder (BYO localhost)";case"minilm":return"MiniLM (experimental)";default:return"Unknown"}},j=s?.currentEmbedder==="trigram"&&e.jsxs("div",{style:t.warning,children:[e.jsx("strong",{children:"Note:"})," TrigramEmbedder is a heuristic-strength default embedder. It works without any setup but recall quality may be lower than a dedicated embedding backend. If you notice poor recall results, consider installing Ollama and running the"," ",e.jsx("code",{children:"/megasetup"})," command, or set"," ",e.jsx("code",{children:"MEGACOMPACT_EMBEDDING_URL"})," to point at your own localhost embedding server."]}),d=r=>{const b=r?"#1a5a1a":"#3a1a1a",y=r?"#4caf50":"#f44336";return e.jsx("span",{style:{...t.badge,background:b,color:y},children:r?"Detected":"Not found"})};return e.jsxs("div",{style:t.container,children:[e.jsx("h2",{style:{fontSize:"1.3rem",marginTop:0,marginBottom:"1rem"},children:"Embedder Setup"}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"Current Embedder Configuration"}),i&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem"},children:["Error: ",i]}),s&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Active Embedder:"}),e.jsx("span",{style:t.value,children:f(s.currentEmbedder)})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Embedding URL:"}),e.jsx("span",{style:t.value,children:s.embeddingUrl??e.jsx("span",{style:{color:"#888"},children:"not set"})})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Embed Cache:"}),e.jsx("span",{style:t.value,children:s.embedCache??e.jsx("span",{style:{color:"#888"},children:"not set"})})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"MiniLM:"}),e.jsx("span",{style:t.value,children:s.minilm?"enabled":"disabled"})]}),j]}),!s&&!i&&e.jsx("p",{style:{color:"#888",fontSize:"0.85rem"},children:"Loading configuration..."})]}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"Local Embedder Detection"}),e.jsx("p",{style:{fontSize:"0.85rem",color:"#a0a0c0",marginTop:0},children:"Detect which local embedding backends are available on this machine. No software is installed — detection only."}),n&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"Ollama"})}),n.ollama!==null?e.jsxs("span",{children:[d(n.ollama.installed),n.ollama.installed&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.ollama.running?`server running, ${n.ollama.models.length} model(s)`:"server not running"})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"llama.cpp"})}),n.llamaCpp!==null?e.jsxs("span",{children:[d(n.llamaCpp.installed),n.llamaCpp.installed&&n.llamaCpp.detail&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.llamaCpp.detail})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"ONNX Runtime"})}),n.onnx!==null?e.jsxs("span",{children:[d(n.onnx.installed),n.onnx.installed&&n.onnx.detail&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.onnx.detail})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),n.error&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.8rem",marginTop:"0.5rem"},children:["Detection error: ",n.error]})]}),c&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem"},children:["Error: ",c]}),e.jsxs("div",{style:{marginTop:"1rem"},children:[e.jsx("button",{style:t.button,onClick:g,disabled:h,children:h?"Detecting...":"Run Detection"}),e.jsxs("span",{style:{fontSize:"0.8rem",color:"#888",marginLeft:"0.75rem"},children:["Or run ",e.jsx("code",{children:"/megasetup"})," in the chat for an interactive wizard."]})]})]}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"How to Upgrade"}),e.jsxs("ul",{style:{fontSize:"0.85rem",lineHeight:1.6,paddingLeft:"1.25rem"},children:[e.jsxs("li",{children:[e.jsx("strong",{children:"Ollama:"})," Install from"," ",e.jsx("a",{href:"https://ollama.com",style:{color:"#6a9fff"},target:"_blank",rel:"noopener noreferrer",children:"ollama.com"}),", then ",e.jsx("code",{children:"ollama pull nomic-embed-text"})," and restart pi. Set ",e.jsx("code",{children:"MEGACOMPACT_EMBEDDING_URL=http://localhost:11434/api/embeddings"}),"."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"llama.cpp:"})," Build or install llama.cpp, run"," ",e.jsx("code",{children:"llama-server"})," with an embedding model, and set the URL accordingly."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"ONNX Runtime:"})," Install"," ",e.jsx("code",{children:"onnxruntime-node"})," in the project root. Not yet integrated — see docs for manual setup."]})]})]})]})}export{w as default};
2
- //# sourceMappingURL=SetupTab-mMlfunJa.js.map