pi-mega-compact 0.11.7 → 0.11.9

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 (93) hide show
  1. package/dist/extensions/dashboard-server/gather-debug-keys.js +18 -0
  2. package/dist/extensions/dashboard-server/gather-debug.js +129 -0
  3. package/dist/extensions/dashboard-server/routes-maintenance.js +6 -0
  4. package/dist/extensions/dashboard-server/routes-memory-map.js +8 -15
  5. package/dist/extensions/mega-commands.js +65 -5
  6. package/dist/extensions/mega-config.js +7 -0
  7. package/dist/extensions/mega-events/agent-handlers.js +74 -5
  8. package/dist/extensions/mega-events/context-handler.js +132 -3
  9. package/dist/extensions/mega-runtime/snapshot.js +1 -1
  10. package/dist/extensions/mega-trim.js +10 -0
  11. package/dist/src/config/turns.js +2 -1
  12. package/dist/src/memoryGraph/embedding.js +50 -0
  13. package/dist/src/memoryGraph/gates.js +379 -0
  14. package/dist/src/memoryGraph/sources.js +390 -0
  15. package/dist/src/memoryGraph.js +133 -259
  16. package/dist/src/store/sqlite/embedding-cache.js +71 -0
  17. package/dist/src/store/sqlite.js +1 -0
  18. package/dist/src/topics/cluster.js +10 -2
  19. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-DFitrb4Y.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-DFitrb4Y.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-PJrmq8pa.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-PJrmq8pa.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js +2 -0
  24. package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js.map +1 -0
  25. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-Dx-RpO8C.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-Dx-RpO8C.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-C63BEWEs.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-C63BEWEs.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-DHpJE6km.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-DHpJE6km.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js +2 -0
  32. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js.map +1 -0
  33. package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js +2 -0
  34. package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js.map +1 -0
  35. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-DCahxNOQ.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-DCahxNOQ.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-RYvO1SZL.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-RYvO1SZL.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-BDGr_UXb.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-BDGr_UXb.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-ByrTnHaB.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-ByrTnHaB.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-CELafKis.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-CELafKis.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-BLWtmfr_.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-BLWtmfr_.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js +2 -0
  48. package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js.map +1 -0
  49. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-D7mgXDe2.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-D7mgXDe2.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-h3jgEs6G.js} +10 -10
  52. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-h3jgEs6G.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-C5lmipBF.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-C5lmipBF.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/index.html +1 -1
  56. package/extensions/dashboard-client/src/api/client.ts +5 -0
  57. package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
  58. package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
  59. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +29 -2
  60. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +101 -0
  61. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
  62. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
  63. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
  64. package/extensions/dashboard-server/api-contracts/index.ts +3 -1
  65. package/extensions/dashboard-server/api-contracts/maintenance.ts +13 -0
  66. package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
  67. package/extensions/dashboard-server/gather-debug-keys.ts +18 -0
  68. package/extensions/dashboard-server/gather-debug.ts +136 -0
  69. package/extensions/dashboard-server/routes-maintenance.ts +7 -0
  70. package/extensions/dashboard-server/routes-memory-map.ts +21 -29
  71. package/extensions/mega-commands.ts +63 -6
  72. package/extensions/mega-config.ts +22 -0
  73. package/extensions/mega-events/agent-handlers.ts +88 -5
  74. package/extensions/mega-events/context-handler.ts +152 -3
  75. package/extensions/mega-runtime/snapshot.ts +1 -1
  76. package/extensions/mega-trim.ts +15 -0
  77. package/package.json +1 -1
  78. package/src/config/turns.ts +5 -2
  79. package/src/memoryGraph/embedding.ts +63 -0
  80. package/src/memoryGraph/gates.ts +451 -0
  81. package/src/memoryGraph/sources.ts +484 -0
  82. package/src/memoryGraph.ts +188 -350
  83. package/src/store/sqlite/embedding-cache.ts +81 -0
  84. package/src/store/sqlite.ts +1 -0
  85. package/src/topics/cluster.ts +11 -1
  86. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +0 -2
  87. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +0 -1
  88. package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js +0 -2
  89. package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js.map +0 -1
  90. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
  91. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
  92. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
  93. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
@@ -1,299 +1,173 @@
1
1
  /**
2
- * memoryGraph.ts — S46 visual memory map: build a graph of graphs.
2
+ * memoryGraph.ts — S46/D3 visual memory map: build a graph of graphs.
3
3
  *
4
- * Nodes = checkpoints (with optional RAPTOR level annotations).
5
- * Edges = temporal adjacency, semantic similarity (cosine above threshold), and
6
- * RAPTOR parent–child links.
4
+ * Delegated-shell pattern: implementation lives in memoryGraph/gates.ts,
5
+ * memoryGraph/sources.ts, and memoryGraph/embedding.ts. This file owns
6
+ * the public types, the entry point (buildMemoryGraph), and exports.
7
7
  *
8
8
  * pi-agnostic (no pi runtime types). Zero network calls (PREVENT-PI-004).
9
- * Synchronous read-only interface non-fatal on store errors.
9
+ * Synchronous read-only interface -- non-fatal on store errors.
10
10
  *
11
11
  * Re-exports the public types as both MemoryGraph* and shorter aliases
12
12
  * (GraphNode, GraphEdge, GraphMetadata) so dashboard api-contracts can import
13
13
  * from the canonical source.
14
14
  */
15
- import { getStateDir, normalizeSessionId } from "./store.js";
16
- import { openStore, decodeEmbedding } from "./store/sqlite/utils.js";
17
- import { listRaptorNodes } from "./store/sqlite/raptor.js";
15
+ import { getStateDir } from "./store.js";
16
+ import { openStore } from "./store/sqlite/utils.js";
18
17
  import { Logger } from "./log.js";
18
+ import { runValidationPipeline } from "./memoryGraph/gates.js";
19
+ import { buildCheckpointNodes, buildTurnNodes, buildTurnContentNodes, buildMemoryNodes, addRaptorAnnotations, deduplicateEdges, areTurnsEnabled, isTurnContentEnabled, isTurnContentFlaggedOn, areMemoriesEnabled, } from "./memoryGraph/sources.js";
19
20
  const log = new Logger();
20
21
  // ---------------------------------------------------------------------------
21
- // Defaults
22
- // ---------------------------------------------------------------------------
23
- const DEFAULT_SIMILARITY_THRESHOLD = 0.7;
24
- const DEFAULT_MAX_EDGES_PER_NODE = 4;
25
- // ---------------------------------------------------------------------------
26
- // Public API
22
+ // Public entry point
27
23
  // ---------------------------------------------------------------------------
28
24
  /**
29
- * Build a memory graph from the store.
25
+ * Build a memory graph from three sources (checkpoints / turns / memories)
26
+ * wired through a 9-gate validation pipeline.
30
27
  *
31
- * Opens the SQLite store, reads all (or session-filtered) checkpoints,
32
- * computes cosine similarity edges, temporal edges, and RAPTOR parent edges.
33
- * Returns a MemoryGraph or an empty graph on failure (non-fatal).
28
+ * Returns an empty graph (byte-identical pre-change for checkpoint-only) when
29
+ * no sources are available.
34
30
  */
35
- export function buildMemoryGraph(opts = {}) {
36
- const { similarityThreshold = DEFAULT_SIMILARITY_THRESHOLD, maxEdgesPerNode = DEFAULT_MAX_EDGES_PER_NODE, sessionId, stateDir = getStateDir(), includeRaptorEdges = true, includeTemporalEdges = true, } = opts;
37
- const builtAt = Date.now();
38
- try {
39
- const nodes = loadNodes(sessionId, stateDir);
40
- const nodeIndex = buildNodeIndex(nodes);
41
- const edges = [];
42
- // Temporal edges: consecutive checkpoints within the same session
43
- if (includeTemporalEdges) {
44
- edges.push(...buildTemporalEdges(nodes));
45
- }
46
- // Semantic edges: cosine similarity above threshold
47
- edges.push(...buildSemanticEdges(nodes, nodeIndex, similarityThreshold, maxEdgesPerNode));
48
- // RAPTOR parent–child edges
49
- if (includeRaptorEdges) {
50
- edges.push(...buildRaptorEdges(nodes, sessionId, stateDir, nodeIndex));
51
- }
52
- // Deduplicate edges (keep highest weight for each source-target pair)
53
- const dedupedEdges = deduplicateEdges(edges);
54
- // Count sessions
55
- const sessionSet = new Set();
56
- for (const n of nodes) {
57
- sessionSet.add(n.sessionId);
58
- }
59
- const meta = {
60
- sessionCount: sessionSet.size,
61
- totalCheckpoints: nodes.length,
62
- totalEdges: dedupedEdges.length,
63
- semanticEdgeCount: dedupedEdges.filter((e) => e.type === "semantic").length,
64
- temporalEdgeCount: dedupedEdges.filter((e) => e.type === "temporal").length,
65
- raptorEdgeCount: dedupedEdges.filter((e) => e.type === "raptor_parent").length,
66
- similarityThresholdUsed: similarityThreshold,
67
- builtAt,
68
- };
69
- return { nodes, edges: dedupedEdges, metadata: meta };
31
+ export function buildMemoryGraph(sessionId, stateDir = getStateDir()) {
32
+ const { nodes, edges } = buildUnvalidatedGraph(sessionId, stateDir);
33
+ // Gate pipeline -- filters, deduplicates, and validates
34
+ const { ws, stats } = runValidationPipeline({ nodes, edges });
35
+ const nodeTypeBreakdown = {};
36
+ const edgeTypeBreakdown = {};
37
+ for (const n of ws.nodes) {
38
+ nodeTypeBreakdown[n.nodeType] = (nodeTypeBreakdown[n.nodeType] ?? 0) + 1;
70
39
  }
71
- catch (err) {
72
- log.warn("buildMemoryGraph failed", { error: String(err) });
73
- return {
74
- nodes: [],
75
- edges: [],
76
- metadata: {
77
- sessionCount: 0,
78
- totalCheckpoints: 0,
79
- totalEdges: 0,
80
- semanticEdgeCount: 0,
81
- temporalEdgeCount: 0,
82
- raptorEdgeCount: 0,
83
- similarityThresholdUsed: similarityThreshold,
84
- builtAt,
85
- },
86
- };
40
+ for (const e of ws.edges) {
41
+ edgeTypeBreakdown[e.type] = (edgeTypeBreakdown[e.type] ?? 0) + 1;
87
42
  }
88
- }
89
- // ---------------------------------------------------------------------------
90
- // Node loading
91
- // ---------------------------------------------------------------------------
92
- function loadNodes(sessionId, stateDir) {
93
- const db = openStore(stateDir);
94
- const sql = sessionId
95
- ? "SELECT * FROM context_chunks WHERE session_id = ? ORDER BY id ASC"
96
- : "SELECT * FROM context_chunks ORDER BY session_id, id ASC";
97
- const rows = sessionId
98
- ? db.prepare(sql).all(normalizeSessionId(sessionId))
99
- : db.prepare(sql).all();
100
- return rows.map(rowToGraphNode);
101
- }
102
- function rowToGraphNode(row) {
103
- const summary = row.summary ?? "";
43
+ const avgWeight = ws.edges.length > 0
44
+ ? ws.edges.reduce((s, e) => s + e.weight, 0) / ws.edges.length
45
+ : 0;
46
+ const sources = countSourcesByType(ws.nodes);
47
+ const validation = {
48
+ gatesRun: stats.gatesRun.length,
49
+ gatesPassed: stats.gatesPassed.length,
50
+ dropped: stats.dropped,
51
+ warnings: stats.warnings.map((w) => ({
52
+ gate: w.gate,
53
+ code: w.code,
54
+ count: w.count,
55
+ })),
56
+ sources,
57
+ builtAt: Date.now(),
58
+ };
104
59
  return {
105
- id: row.id,
106
- sessionId: row.session_id,
107
- label: row.id,
108
- summaryTruncated: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
109
- tokenEstimate: row.token_estimate ?? 0,
110
- timestamp: row.created_at ?? Date.now(),
111
- dedupStatus: row.dedup_status ?? undefined,
112
- raptorLevel: 0, // enriched below if RAPTOR data exists
113
- topicSummary: row.topic_summary ?? undefined,
114
- decisionCount: row.key_decisions
115
- ? safeJsonCount(row.key_decisions)
116
- : 0,
117
- textSnippet: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
60
+ nodes: ws.nodes,
61
+ edges: ws.edges,
62
+ metadata: {
63
+ totalNodes: ws.nodes.length,
64
+ totalEdges: ws.edges.length,
65
+ avgWeight,
66
+ nodeTypeBreakdown,
67
+ edgeTypeBreakdown,
68
+ },
69
+ validation,
118
70
  };
119
71
  }
120
- /** Parse a JSON array stored in a text column and return its length. */
121
- function safeJsonCount(raw) {
122
- if (!raw)
123
- return 0;
124
- try {
125
- const arr = JSON.parse(raw);
126
- return Array.isArray(arr) ? arr.length : 0;
127
- }
128
- catch {
129
- return 0;
130
- }
131
- }
132
72
  // ---------------------------------------------------------------------------
133
- // Node index building
73
+ // Source orchestrator
134
74
  // ---------------------------------------------------------------------------
135
- function buildNodeIndex(nodes) {
136
- const idx = new Map();
137
- nodes.forEach((n, i) => idx.set(n.id, i));
138
- return idx;
139
- }
140
- // ---------------------------------------------------------------------------
141
- // Edges: temporal
142
- // ---------------------------------------------------------------------------
143
- function buildTemporalEdges(nodes) {
75
+ function buildUnvalidatedGraph(sessionId, stateDir) {
76
+ const nodes = [];
144
77
  const edges = [];
145
- if (nodes.length < 2)
146
- return edges;
147
- let prev = nodes[0];
148
- for (let i = 1; i < nodes.length; i++) {
149
- const cur = nodes[i];
150
- if (cur.sessionId === prev.sessionId) {
151
- edges.push({
152
- source: prev.id,
153
- target: cur.id,
154
- weight: 1.0,
155
- type: "temporal",
156
- });
157
- }
158
- prev = cur;
78
+ let db;
79
+ try {
80
+ db = openStore(stateDir);
159
81
  }
160
- return edges;
161
- }
162
- // ---------------------------------------------------------------------------
163
- // Edges: semantic (cosine similarity)
164
- // ---------------------------------------------------------------------------
165
- function buildSemanticEdges(nodes, _nodeIndex, threshold, maxPerNode) {
166
- if (nodes.length < 2)
167
- return [];
168
- const db = openStore();
169
- // Retrieve embeddings from DB for each node
170
- const embeddings = new Map();
171
- const placeholders = nodes.map(() => "?").join(",");
172
- const ids = nodes.map((n) => n.id);
173
- const rows = db
174
- .prepare(`SELECT id, embedding_blob FROM context_chunks WHERE id IN (${placeholders})`)
175
- .all(...ids);
176
- for (const row of rows) {
177
- const emb = decodeEmbedding(row.embedding_blob);
178
- if (emb.length > 0) {
179
- embeddings.set(row.id, emb);
180
- }
82
+ catch {
83
+ log.warn("graph_source_unavailable", { detail: "Cannot open store" });
84
+ return { nodes: [], edges: [] };
181
85
  }
182
- const edges = [];
183
- for (const node of nodes) {
184
- const aId = node.id;
185
- const aEmb = embeddings.get(aId);
186
- if (!aEmb)
187
- continue;
188
- const candidates = [];
189
- for (const other of nodes) {
190
- if (other.id === aId)
191
- continue;
192
- // Skip if already connected via temporal or raptor edge
193
- const bEmb = embeddings.get(other.id);
194
- if (!bEmb)
195
- continue;
196
- const sim = cosineSimilarity(aEmb, bEmb);
197
- if (sim >= threshold) {
198
- candidates.push({ target: other.id, weight: sim });
86
+ try {
87
+ // Source: checkpoints (always on)
88
+ buildCheckpointNodes(db, sessionId, nodes, edges);
89
+ // Source A: turns (structural, metadata-only)
90
+ if (areTurnsEnabled()) {
91
+ try {
92
+ buildTurnNodes(db, sessionId, nodes, edges);
93
+ }
94
+ catch (err) {
95
+ log.warn("graph_source_unavailable", {
96
+ source: "turns",
97
+ detail: String(err),
98
+ });
199
99
  }
200
100
  }
201
- // Keep top-K highest-weight semantic neighbors
202
- candidates.sort((a, b) => b.weight - a.weight);
203
- const top = candidates.slice(0, maxPerNode);
204
- for (const c of top) {
205
- edges.push({
206
- source: aId,
207
- target: c.target,
208
- weight: c.weight,
209
- type: "semantic",
210
- });
101
+ // Source B: turn content (raw_transcript join, embedding-based)
102
+ if (isTurnContentEnabled()) {
103
+ try {
104
+ buildTurnContentNodes(db, sessionId, nodes, edges);
105
+ }
106
+ catch (err) {
107
+ log.warn("graph_source_unavailable", {
108
+ source: "turn-content",
109
+ detail: String(err),
110
+ });
111
+ }
211
112
  }
212
- }
213
- return edges;
214
- }
215
- // ---------------------------------------------------------------------------
216
- // Edges: RAPTOR parent–child
217
- // ---------------------------------------------------------------------------
218
- function buildRaptorEdges(nodes, sessionId, stateDir, nodeIndex) {
219
- const edges = [];
220
- try {
221
- if (sessionId) {
222
- enrichRaptorForSession(sessionId, stateDir, nodeIndex, nodes, edges);
113
+ else if (isTurnContentFlaggedOn() && !isTurnContentEnabled()) {
114
+ log.warn("graph_source_unavailable", {
115
+ source: "turn-content",
116
+ detail: "dbMirror is OFF -- no raw_transcript available",
117
+ });
223
118
  }
224
- else {
225
- // Load all sessions from DB
226
- const db = openStore(stateDir);
227
- const sessionRows = db
228
- .prepare("SELECT DISTINCT session_id FROM raptor_nodes ORDER BY session_id")
229
- .all();
230
- for (const row of sessionRows) {
231
- enrichRaptorForSession(row.session_id, stateDir, nodeIndex, nodes, edges);
119
+ // Source C: memories
120
+ if (areMemoriesEnabled()) {
121
+ try {
122
+ buildMemoryNodes(db, sessionId, nodes, edges);
123
+ }
124
+ catch (err) {
125
+ log.warn("graph_source_unavailable", {
126
+ source: "memories",
127
+ detail: String(err),
128
+ });
232
129
  }
233
130
  }
234
- }
235
- catch (err) {
236
- log.warn("RAPTOR edge build failed (non-fatal)", { error: String(err) });
237
- }
238
- return edges;
239
- }
240
- function enrichRaptorForSession(sessId, stateDir, nodeIndex, nodes, edges) {
241
- const raptorNodes = listRaptorNodes(sessId, stateDir);
242
- for (const rn of raptorNodes) {
243
- const idx = nodeIndex.get(rn.id);
244
- if (idx !== undefined) {
245
- // Annotate the graph node with the RAPTOR level
246
- nodes[idx].raptorLevel = rn.level;
131
+ // RAPTOR annotations on checkpoint nodes
132
+ try {
133
+ addRaptorAnnotations(sessionId, stateDir, nodes, edges);
247
134
  }
248
- if (!rn.parentId)
249
- continue;
250
- // Only create edge if both nodes are in the graph
251
- if (nodeIndex.has(rn.id) && nodeIndex.has(rn.parentId)) {
252
- edges.push({
253
- source: rn.id,
254
- target: rn.parentId,
255
- weight: Math.max(0.1, 1.0 - rn.level * 0.1), // higher levels = weaker link
256
- type: "raptor_parent",
257
- });
135
+ catch {
136
+ // non-fatal
258
137
  }
259
138
  }
260
- }
261
- // ---------------------------------------------------------------------------
262
- // Edge deduplication
263
- // ---------------------------------------------------------------------------
264
- function deduplicateEdges(edges) {
265
- const seen = new Map();
266
- for (const e of edges) {
267
- const keyA = `${e.source}|${e.target}`;
268
- const keyB = `${e.target}|${e.source}`;
269
- const existing = seen.get(keyA) ?? seen.get(keyB);
270
- if (!existing) {
271
- seen.set(keyA, e);
272
- }
273
- else if (e.weight > existing.weight) {
274
- // Replace with higher-weight edge
275
- seen.delete(keyA);
276
- seen.delete(keyB);
277
- seen.set(keyA, e);
278
- }
139
+ finally {
140
+ // DatabaseSync has no close in Node 22 -- leave GC to clean up
279
141
  }
280
- return Array.from(seen.values());
142
+ // Pre-gate edge deduplication
143
+ const deduped = deduplicateEdges(edges);
144
+ edges.length = 0;
145
+ edges.push(...deduped);
146
+ return { nodes, edges };
281
147
  }
282
148
  // ---------------------------------------------------------------------------
283
- // Cosine similarity
284
- // ---------------------------------------------------------------------------
285
- function cosineSimilarity(a, b) {
286
- const len = Math.min(a.length, b.length);
287
- if (len === 0)
288
- return 0;
289
- let dot = 0;
290
- let normA = 0;
291
- let normB = 0;
292
- for (let i = 0; i < len; i++) {
293
- dot += a[i] * b[i];
294
- normA += a[i] * a[i];
295
- normB += b[i] * b[i];
149
+ // Helpers
150
+ // ---------------------------------------------------------------------------
151
+ function countSourcesByType(nodes) {
152
+ let checkpoint = 0;
153
+ let turn = 0;
154
+ let turnContent = 0;
155
+ let memory = 0;
156
+ for (const n of nodes) {
157
+ switch (n.nodeType) {
158
+ case "checkpoint":
159
+ checkpoint++;
160
+ break;
161
+ case "turn":
162
+ turn++;
163
+ break;
164
+ case "turn-content":
165
+ turnContent++;
166
+ break;
167
+ case "memory":
168
+ memory++;
169
+ break;
170
+ }
296
171
  }
297
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
298
- return denom === 0 ? 0 : dot / denom;
172
+ return { checkpoint, turn, turnContent, memory };
299
173
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * embedding-cache.ts — read-or-compute embedding with content-hash caching.
3
+ *
4
+ * The `embedding_cache` table (schema at schema.ts:421) avoids re-embedding the
5
+ * same content on every graph build. D3 Source B/C callers first hit this cache;
6
+ * only cache misses run the embedder.
7
+ *
8
+ * Non-fatal: all errors are caught and return [] so the graph build never breaks.
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { defaultEmbedder } from "../../embedder.js";
12
+ import { encodeEmbedding } from "./utils.js";
13
+ /**
14
+ * Decode a Float32 BLOB back to a number[].
15
+ *
16
+ * Mirrors decodeEmbedding from utils.ts but accepts a Buffer explicitly (the
17
+ * type node:sqlite returns for BLOB columns). Also handles Uint8Array (the
18
+ * common node:sqlite runtime type) via DataView.
19
+ *
20
+ * Returns an empty array for null/undefined/zero-length input.
21
+ */
22
+ export function decodeEmbeddingBlob(buf) {
23
+ if (!buf || buf.byteLength === 0)
24
+ return [];
25
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
26
+ const n = buf.byteLength / 4;
27
+ const out = new Array(n);
28
+ for (let i = 0; i < n; i++)
29
+ out[i] = dv.getFloat32(i * 4, true);
30
+ return out;
31
+ }
32
+ /**
33
+ * Read an embedding for `content` from the cache, or compute and store it.
34
+ *
35
+ * 1. Hash content with SHA-256 → hex digest (matches the content_hash convention).
36
+ * 2. SELECT from embedding_cache — hit → decode blob → return number[].
37
+ * 3. Miss → embed via defaultEmbedder() → encode → INSERT OR REPLACE → return.
38
+ *
39
+ * Non-fatal: any error is caught, logged, and returns [].
40
+ *
41
+ * @param db - Open node:sqlite DatabaseSync (embedding_cache table must exist).
42
+ * @param content - Text to embed (e.g. a context chunk's raw text).
43
+ * @returns The 512‑dim embedding vector, or [] on cache-hit decode failure or
44
+ * any error.
45
+ */
46
+ export function getOrComputeEmbedding(db, content) {
47
+ try {
48
+ const hash = createHash("sha256").update(content).digest("hex");
49
+ // Parameterized query — PREVENT-002
50
+ const row = db
51
+ .prepare("SELECT embedding FROM embedding_cache WHERE content_hash = ?")
52
+ .get(hash);
53
+ if (row) {
54
+ return decodeEmbeddingBlob(row.embedding);
55
+ }
56
+ // Cache miss: embed, store, return
57
+ const embedder = defaultEmbedder();
58
+ const vec = embedder.embed(content);
59
+ const blob = encodeEmbedding(vec);
60
+ db.prepare("INSERT OR REPLACE INTO embedding_cache (content_hash, embedding, computed_at) VALUES (?, ?, ?)").run(hash, blob, Date.now());
61
+ return vec;
62
+ }
63
+ catch (err) {
64
+ // Non-fatal: log and degrade gracefully
65
+ const msg = err instanceof Error ? err.message : String(err);
66
+ // Logger not imported here to keep this leaf self-contained; stderr is
67
+ // acceptable for an error path that signals a bug, not a user‑facing log.
68
+ process.stderr.write(`[embedding-cache] getOrComputeEmbedding error: ${msg}\n`);
69
+ return [];
70
+ }
71
+ }
@@ -26,3 +26,4 @@ export * from "./sqlite/game-achievements.js";
26
26
  export * from "./sqlite/perf-samples.js";
27
27
  export * from "./sqlite/cache-stripes.js";
28
28
  export * from "./sqlite/turns.js";
29
+ export * from "./sqlite/embedding-cache.js";
@@ -50,10 +50,18 @@ export function loadEmbeddings(mainDb) {
50
50
  * Build a topic model from the real stored chunks. Picks k by silhouette when
51
51
  * computable, else elbow; degenerate/small corpus → single `general` cluster.
52
52
  * Never throws on degenerate input — returns the safe single-cluster model.
53
+ *
54
+ * When `source` is provided, uses those pre-loaded EmbeddedChunks instead of
55
+ * reading from `mainDb` (used by D1 seed path from raw_transcript). Source can
56
+ * be partial — the function still falls back to mainDb for additional chunks
57
+ * when source is non-empty but mainDb also has rows. Default behavior unchanged
58
+ * when source is omitted (backward-compatible).
53
59
  */
54
- export function buildTopicModel(mainDb, cfg = DEFAULT_CLUSTER_CONFIG) {
60
+ export function buildTopicModel(mainDb, cfg = DEFAULT_CLUSTER_CONFIG, source) {
55
61
  const builtAt = Date.now();
56
- const chunks = loadEmbeddings(mainDb);
62
+ const chunks = source !== undefined && source.length > 0
63
+ ? source
64
+ : loadEmbeddings(mainDb);
57
65
  const totalChunks = chunks.length;
58
66
  // Too small to cluster meaningfully → single general cluster.
59
67
  if (totalChunks < Math.max(2, cfg.kRange[0])) {
@@ -1,2 +1,2 @@
1
- import{r as o,j as n}from"./index-DrktNOoT.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
2
- //# sourceMappingURL=AchievementTiles-DR9cLGDL.js.map
1
+ import{r as o,j as n}from"./index-h3jgEs6G.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
2
+ //# sourceMappingURL=AchievementTiles-DFitrb4Y.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementTiles-DR9cLGDL.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\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\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
1
+ {"version":3,"file":"AchievementTiles-DFitrb4Y.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\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\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
@@ -1,2 +1,2 @@
1
- import{r as t,n as m,j as s}from"./index-DrktNOoT.js";import{A as v}from"./AchievementTiles-DR9cLGDL.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
2
- //# sourceMappingURL=AchievementsTab-svNUgJ1e.js.map
1
+ import{r as t,n as m,j as s}from"./index-h3jgEs6G.js";import{A as v}from"./AchievementTiles-DFitrb4Y.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
2
+ //# sourceMappingURL=AchievementsTab-PJrmq8pa.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementsTab-svNUgJ1e.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
1
+ {"version":3,"file":"AchievementsTab-PJrmq8pa.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
@@ -0,0 +1,2 @@
1
+ import{j as e,r as h,u as j,k as R,l as P,i as E,c as $}from"./index-h3jgEs6G.js";import{C as L,T as A}from"./TimeSavedCard-BLWtmfr_.js";import{b as v,c as u,d as H,e as M,f as o}from"./format-COjDIVGj.js";import{l as I}from"./pricing-CvE4NllQ.js";function N(a){return a>=80?"ov-stat-value-green":a>=50?"ov-stat-value-yellow":"ov-stat-value-red"}function k(a){return a==null?"":a>=0?"ov-stat-value-green":"ov-stat-value-red"}function r({label:a,value:s,className:t,title:l}){return e.jsxs("div",{className:"ov-stat-row",children:[e.jsx("span",{className:"ov-stat-label",title:l,children:a}),e.jsx("span",{className:`ov-stat-value${t?` ${t}`:""}`,children:s})]})}function w(a){return e.jsxs("div",{className:"card provider-cache-card",children:[e.jsx(r,{label:"Cache Hit Rate",value:`${a.hitPct.toFixed(1)}%`,className:N(a.hitPct)}),e.jsx(r,{label:"Turns Recorded",value:String(a.turnCount)}),e.jsx(r,{label:"Cache Read Tokens",value:v(a.totalCacheRead)}),e.jsx(r,{label:"Cache Write Tokens",value:v(a.totalCacheWrite)}),e.jsx(r,{label:"Input Tokens",value:v(a.totalInput)}),e.jsx(r,{label:"$ Saved (reads)",value:u(a.cacheReadSaved)}),e.jsx(r,{label:"Write Investment",value:u(a.cacheWriteCost)}),e.jsx(r,{label:"Net Saved",value:u(a.netSaved),className:k(a.netSaved)}),e.jsx(r,{label:"Model",value:a.modelLabel??"—"}),e.jsx(r,{label:"Tracked Since",value:H(a.firstTurnAt)}),e.jsx(r,{label:"Last Updated",value:M(a.latestTurnAt)}),a.byModel.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-stat-section-label",children:"Per Model"}),a.byModel.map(s=>{const t=I(s.model),l=t!=null?s.totalCacheRead/1e6*t:null,n=t!=null?s.totalCacheWrite/1e6*t:null;return e.jsxs("div",{className:"ov-stat-group",children:[e.jsx(r,{label:"Model",value:s.model}),e.jsx(r,{label:"Hit Rate",value:`${s.hitPct.toFixed(1)}%`,className:N(s.hitPct)}),e.jsx(r,{label:"Cache Read",value:v(s.totalCacheRead)}),e.jsx(r,{label:"Cache Write",value:v(s.totalCacheWrite)}),e.jsx(r,{label:"Samples",value:String(s.sampleCount)}),e.jsx(r,{label:"$ Saved (reads)",value:l!=null?u(l):"—"}),e.jsx(r,{label:"Write Investment",value:n!=null?u(n):"—"})]},s.model)})]})]})}const W=["ov-bar-green","ov-bar-blue","ov-bar-teal","ov-bar-yellow","ov-bar-gray"],D=["Permanent","Epoch","Topic","Thread","Volatile"],_={good:"ov-text-green",fair:"ov-text-yellow",degraded:"ov-text-orange",poor:"ov-text-red"};function F({data:a}){const s=Math.max(...a.buckets.map(l=>l.count),1),t=_[a.health.label]??"ov-text-gray";return e.jsxs("div",{className:"ov-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Stripe Distribution"}),e.jsxs("div",{className:"ov-stripe-health-summary",children:[e.jsx("span",{className:`ov-stripe-health-badge ${t}`,children:a.health.label}),e.jsx("span",{className:"ov-stripe-health-score",children:o(a.health.score)}),e.jsxs("span",{className:"ov-stripe-churn",children:["Churn: ",o(a.health.churnRate)]}),e.jsxs("span",{className:"ov-stripe-total",children:[a.totalChunks," total chunks"]})]}),e.jsx("div",{className:"ov-stripe-bars",children:a.buckets.map(l=>{const n=s>0?Math.max(0,Math.min(100,l.count/s*100)):0,c=W[l.stripe]??"ov-bar-gray",i=l.label||(D[l.stripe]??`Stripe ${l.stripe}`);return e.jsxs("div",{className:"ov-stripe-row",children:[e.jsx("span",{className:"ov-stripe-label",title:i,children:i}),e.jsx("div",{className:"ov-bar-track",children:e.jsx("div",{className:`ov-bar-fill ${c}`,style:{width:`${n}%`}})}),e.jsx("span",{className:"ov-stripe-count",children:l.count}),e.jsx("span",{className:"ov-stripe-stability",title:`min ${o(l.minStability)} / max ${o(l.maxStability)}`,children:o(l.avgStability)})]},l.stripe)})}),e.jsxs("div",{className:"ov-stripe-footer",children:["Epoch: ",a.epochId??"—","· Dominant tier: ",o(a.health.dominantTier)]})]})}function m(a){return`${a.toFixed(1)}%`}function O(a){return a>=80?"ov-bar-green":a>=60?"ov-bar-teal":a>=40?"ov-bar-yellow":a>=20?"ov-bar-orange":"ov-bar-red"}const G=({perf:a})=>{const{cache_hit_pct:s}=a,t=s.samples&&s.samples.length>0?s.samples:[];return e.jsxs("div",{className:"ov-card cache-trend-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Hit-Rate Trend"}),e.jsxs("div",{className:"ov-perf-summary",children:[e.jsxs("div",{className:"ov-perf-stat",children:["Avg: ",e.jsx("strong",{children:m(s.avg)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Latest: ",e.jsx("strong",{children:m(s.latest)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Samples: ",e.jsx("strong",{children:s.n})]})]}),t.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Cache hit rate trend: ${s.n} samples, average ${m(s.avg)}, latest ${m(s.latest)}`,children:t.map((l,n)=>{const c=Math.max(2,Math.round(l.pct*.6)),i=O(l.pct),x=n===t.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${i}${x?" ov-bar-latest":""}`,style:{height:`${c}px`},title:`${m(l.pct)} at ${new Date(l.ts).toLocaleTimeString()}`},n)})}),e.jsxs("div",{className:"ov-card-footer",children:[t.length," sample",t.length!==1?"s":""," over"," ",t.length>1?`${Math.round((t[t.length-1].ts-t[0].ts)/1e3)}s`:"latest turn"]})]}):e.jsx("div",{className:"ov-empty-state",children:"No cache-hit samples yet"})]})};function q(){const[a,s]=h.useState(!1),{data:t,loading:l,error:n}=j(h.useCallback(()=>R(),[]),{pollInterval:5e3}),{data:c,loading:i,error:x}=j(h.useCallback(()=>P(),[]),{pollInterval:15e3}),{data:g,loading:T,error:C}=j(h.useCallback(()=>E({minutes:30}),[]),{pollInterval:15e3}),{data:d,loading:y,error:S}=j(h.useCallback(()=>$(),[]),{pollInterval:5e3});return e.jsxs("div",{className:"cache-tab",children:[e.jsxs("div",{className:"card-section",children:[e.jsx("button",{type:"button",className:"info-toggle",onClick:()=>s(!a),children:a?"Hide Guidance":"Show Cache-Friendly Prompt Ordering Guidance"}),a&&e.jsxs("div",{className:"info-panel",children:[e.jsx("h3",{children:"Cache-Friendly Prompt Ordering"}),e.jsxs("ul",{children:[e.jsx("li",{children:"Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix."}),e.jsxs("li",{children:["Tool results and volatile content are automatically moved to the tail by message separation (",e.jsx("code",{children:"MEGACOMPACT_MESSAGE_SEPARATION"}),") so they don't invalidate the cache prefix."]}),e.jsxs("li",{children:["Cache striping (",e.jsx("code",{children:"MEGACOMPACT_CACHE_STRIPING"}),") further orders stable context by a stability score so the most durable chunks lead."]}),e.jsx("li",{children:"Avoid inserting new instructions mid-conversation — prepend them instead."})]}),d&&e.jsxs("div",{className:"cache-status-indicator",children:[e.jsx("strong",{children:"Status:"})," ",d.config.messageSeparation?"Separation Enabled":"Separation Disabled (Check env)"," |"," ",d.config.cacheStriping?"Striping Enabled":"Striping Disabled (Check env)"]})]})]}),e.jsx("h2",{children:"Provider Prompt Cache"}),l?e.jsx("p",{className:"tab-stub",children:"Loading..."}):n?e.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",n.message]}):t?e.jsx(w,{hitPct:t.cache.avgHitPct,turnCount:t.cache.turnCount,totalCacheRead:t.cache.totalCacheRead,totalCacheWrite:t.cache.totalCacheWrite,totalInput:t.cache.totalInput,cacheReadSaved:t.savings?.cacheReadSaved??null,cacheWriteCost:t.savings?.cacheWriteCost??null,netSaved:t.savings?.netSaved??null,modelLabel:t.savings?.model??null,firstTurnAt:t.cache.firstTurnAt,latestTurnAt:t.cache.latestTurnAt,byModel:t.cache.byModel??[]}):null,e.jsx("h2",{children:"Cache Stripe Distribution"}),i?e.jsx("p",{className:"tab-stub",children:"Loading..."}):x?e.jsxs("p",{className:"tab-stub",children:["Cache stripe data unavailable: ",x.message]}):c?e.jsx(F,{data:c}):null,e.jsx("h2",{children:"Cache Hit-Rate Trend"}),T?e.jsx("p",{className:"tab-stub",children:"Loading..."}):C?e.jsxs("p",{className:"tab-stub",children:["Hit-rate trend unavailable: ",C.message]}):g?e.jsx(G,{perf:g}):null,e.jsx("h2",{children:"Mega-Compact Dedup Cache"}),y?e.jsx("p",{className:"tab-stub",children:"Loading..."}):S?e.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",S.message]}):d&&(()=>{const{cacheHits:p,compacts:f,timeSaved:b}=d;return e.jsxs("div",{className:"card-grid overview-card-grid",children:[e.jsx(L,{cacheHitsSession:p.session,cacheHitsTotal:p.total,tokensSavedSession:p.sessionTokensSaved,tokensSavedTotal:p.totalTokensSaved,compactionsSession:f.session,compactionsTotal:f.total}),e.jsx(A,{compactSessionSec:b.compact.sessionSec,compactTotalSec:b.compact.totalSec,cacheHitSessionSec:b.cacheHit.sessionSec,cacheHitTotalSec:b.cacheHit.totalSec})]})})()]})}export{q as default};
2
+ //# sourceMappingURL=CacheTab-C1WE7dEx.js.map