pi-mega-compact 0.12.1 → 0.12.3

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 (57) hide show
  1. package/dist/extensions/dashboard-server/gather-debug.js +63 -0
  2. package/dist/extensions/dashboard-server/routes-game.js +6 -1
  3. package/dist/extensions/dashboard-server/routes-setup.js +30 -4
  4. package/dist/src/memoryGraph/sources.js +49 -20
  5. package/dist/src/memoryGraph.js +17 -5
  6. package/dist/src/store/turns/schema.js +18 -2
  7. package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cwg_Ab8R.js → AchievementTiles-Cntu89Xw.js} +2 -2
  8. package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cwg_Ab8R.js.map → AchievementTiles-Cntu89Xw.js.map} +1 -1
  9. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dk32-4Ha.js → AchievementsTab-4ckY9z4n.js} +2 -2
  10. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dk32-4Ha.js.map → AchievementsTab-4ckY9z4n.js.map} +1 -1
  11. package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js → CacheTab-BfGJkiaf.js} +2 -2
  12. package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js.map → CacheTab-BfGJkiaf.js.map} +1 -1
  13. package/extensions/dashboard-client/dist/assets/{ConfigTab-DYRl3mJZ.js → ConfigTab-DV08rHoV.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{ConfigTab-DYRl3mJZ.js.map → ConfigTab-DV08rHoV.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{EventsTab-r_zXcrmZ.js → EventsTab-DHytZ0xs.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{EventsTab-r_zXcrmZ.js.map → EventsTab-DHytZ0xs.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{GameTab-DjwA_Q1v.js → GameTab-Ofpoz-6s.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{GameTab-DjwA_Q1v.js.map → GameTab-Ofpoz-6s.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{HealthTab-BjH73kQr.js → HealthTab-B0klAxD8.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{HealthTab-BjH73kQr.js.map → HealthTab-B0klAxD8.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DzeSq9vK.js → MaintenanceTab-CHKh3KPI.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DzeSq9vK.js.map → MaintenanceTab-CHKh3KPI.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BU9iM3ir.js → MemoryMapTab-B6FLQ-32.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BU9iM3ir.js.map → MemoryMapTab-B6FLQ-32.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{MetricsTab-lMVodOwY.js → MetricsTab-DvOjTDkW.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{MetricsTab-lMVodOwY.js.map → MetricsTab-DvOjTDkW.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{OverviewTab-D-laa6CW.js → OverviewTab-CqbONEwi.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{OverviewTab-D-laa6CW.js.map → OverviewTab-CqbONEwi.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{ReposTab-5Pn2cOOO.js → ReposTab-sc1GLn7i.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{ReposTab-5Pn2cOOO.js.map → ReposTab-sc1GLn7i.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{SessionsTab-Dtdmvl2C.js → SessionsTab-C5gB45oc.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{SessionsTab-Dtdmvl2C.js.map → SessionsTab-C5gB45oc.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js +2 -0
  34. package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js.map +1 -0
  35. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B5Lw6fTp.js → TimeSavedCard-BBwvb4cC.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B5Lw6fTp.js.map → TimeSavedCard-BBwvb4cC.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{TopicsTab-Ds1IHhPw.js → TopicsTab-BsCf512_.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{TopicsTab-Ds1IHhPw.js.map → TopicsTab-BsCf512_.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{TurnsTab-CG2RDRmh.js → TurnsTab-C3ti7NB5.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{TurnsTab-CG2RDRmh.js.map → TurnsTab-C3ti7NB5.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{index-C9dTXvDQ.js → index-BEeaI9uo.js} +3 -3
  42. package/extensions/dashboard-client/dist/assets/{index-C9dTXvDQ.js.map → index-BEeaI9uo.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{useSSE-B6l8O-yi.js → useSSE-BMSSO9wB.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{useSSE-B6l8O-yi.js.map → useSSE-BMSSO9wB.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/index.html +1 -1
  46. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +19 -0
  47. package/extensions/dashboard-server/api-contracts/maintenance.ts +1 -0
  48. package/extensions/dashboard-server/api-contracts/setup.ts +7 -1
  49. package/extensions/dashboard-server/gather-debug.ts +63 -0
  50. package/extensions/dashboard-server/routes-game.ts +6 -1
  51. package/extensions/dashboard-server/routes-setup.ts +28 -4
  52. package/package.json +1 -1
  53. package/src/memoryGraph/sources.ts +56 -20
  54. package/src/memoryGraph.ts +17 -5
  55. package/src/store/turns/schema.ts +18 -2
  56. package/extensions/dashboard-client/dist/assets/SetupTab-rKGgWpI0.js +0 -2
  57. package/extensions/dashboard-client/dist/assets/SetupTab-rKGgWpI0.js.map +0 -1
@@ -35,6 +35,7 @@ export function sendGatherDebug(res, ctx) {
35
35
  dashboardSnapshot: { error: "not fetched" },
36
36
  recentEvents: [],
37
37
  criticalEvents: [],
38
+ dataSourceAudit: {},
38
39
  };
39
40
  // 1. Config flags (no secrets — MEGACOMPACT_* only)
40
41
  for (const key of MEGACOMPACT_ENV_KEYS) {
@@ -115,6 +116,68 @@ export function sendGatherDebug(res, ctx) {
115
116
  catch (e) {
116
117
  result.recentEvents = [{ error: e instanceof Error ? e.message : String(e) }];
117
118
  }
119
+ // 6. Data source audit — per-tab data population check
120
+ try {
121
+ const audit = {};
122
+ const db = openStoreDb(ctx);
123
+ // Main db tables
124
+ for (const table of ["context_chunks", "memories", "raw_transcript", "checkpoint_epochs", "dedup_mirror"]) {
125
+ try {
126
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get();
127
+ audit[`main.${table}`] = row.c;
128
+ }
129
+ catch {
130
+ audit[`main.${table}`] = "table missing";
131
+ }
132
+ }
133
+ // context_health + cache_poison_events
134
+ for (const table of ["context_health", "cache_poison_events"]) {
135
+ try {
136
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get();
137
+ audit[`main.${table}`] = row.c;
138
+ }
139
+ catch {
140
+ audit[`main.${table}`] = "table missing";
141
+ }
142
+ }
143
+ // turns.db tables
144
+ try {
145
+ const turnMod = require("../../src/store/turns/connection.js");
146
+ const tdb = turnMod.openTurnDb(ctx.stateDir);
147
+ for (const table of ["turns", "turn_recall", "session_conversations", "topics", "memory_topics"]) {
148
+ try {
149
+ const row = tdb.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get();
150
+ audit[`turns.${table}`] = row.c;
151
+ }
152
+ catch {
153
+ audit[`turns.${table}`] = "table missing";
154
+ }
155
+ }
156
+ }
157
+ catch (e) {
158
+ audit["turns.db"] = `error: ${e instanceof Error ? e.message : String(e)}`;
159
+ }
160
+ // .mega-compact.env
161
+ try {
162
+ const envPath = ctx.stateDir + "/.mega-compact.env";
163
+ audit["mega-compact.env"] = existsSync(envPath) ? "exists" : "not found";
164
+ }
165
+ catch {
166
+ audit["mega-compact.env"] = "check failed";
167
+ }
168
+ // dashboard.json
169
+ try {
170
+ const metricsPath = defaultMetricsPath(ctx.stateDir);
171
+ audit["dashboard.json"] = existsSync(metricsPath) ? "exists" : "not found";
172
+ }
173
+ catch {
174
+ audit["dashboard.json"] = "check failed";
175
+ }
176
+ result.dataSourceAudit = audit;
177
+ }
178
+ catch (e) {
179
+ result.dataSourceAudit = { error: e instanceof Error ? e.message : String(e) };
180
+ }
118
181
  // Send (local helper to avoid importing sendJson from a non-exporting module)
119
182
  try {
120
183
  const body = JSON.stringify(result);
@@ -194,7 +194,12 @@ export function handlePerf(req, res, ctx) {
194
194
  minutes = 30;
195
195
  minutes = Math.min(minutes, 1440); // cap at 24h
196
196
  const sinceTs = Date.now() - minutes * 60_000;
197
- const rows = readPerfSamples(stateDir, sinceTs); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
197
+ let rows = readPerfSamples(stateDir, sinceTs); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
198
+ // Fallback: if the window is empty (stale data from a previous
199
+ // session), show the most recent 200 samples regardless of age.
200
+ if (rows.length === 0) {
201
+ rows = readPerfSamples(stateDir, 0).slice(-200);
202
+ }
198
203
  const byKind = new Map();
199
204
  for (const r of rows) {
200
205
  let arr = byKind.get(r.kind);
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { spawnSync } from "node:child_process"; // guardrails-allow PREVENT-PI-004: local subprocess detection only
12
12
  import { createRequire } from "node:module";
13
- import { writeFileSync, mkdirSync } from "node:fs";
13
+ import { writeFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  // ---------------------------------------------------------------------------
16
16
  // handleSetupStatus — "/api/setup-status"
@@ -24,7 +24,28 @@ function detectCurrentEmbedder() {
24
24
  return "minilm";
25
25
  return "trigram";
26
26
  }
27
- export function handleSetupStatus(req, res, _ctx) {
27
+ /** Read the configured embedder from the .mega-compact.env file (written by
28
+ * the configure endpoint). This shows what the user SELECTED even before pi
29
+ * restarts and loads the env file into process.env. */
30
+ function detectConfiguredEmbedder(stateDir) {
31
+ try {
32
+ const envPath = join(stateDir, ".mega-compact.env");
33
+ if (!existsSync(envPath))
34
+ return { embedder: "trigram", url: null };
35
+ const content = readFileSync(envPath, "utf-8");
36
+ const urlMatch = content.match(/^export\s+MEGACOMPACT_EMBEDDING_URL="([^"]+)"/m);
37
+ if (urlMatch)
38
+ return { embedder: "http", url: urlMatch[1] };
39
+ const minilmMatch = content.match(/^export\s+MEGACOMPACT_MINILM="?(.+?)"?\s*$/m);
40
+ if (minilmMatch && ["1", "true", "yes"].includes(minilmMatch[1].trim().toLowerCase()))
41
+ return { embedder: "minilm", url: null };
42
+ return { embedder: "trigram", url: null };
43
+ }
44
+ catch {
45
+ return { embedder: "trigram", url: null };
46
+ }
47
+ }
48
+ export function handleSetupStatus(req, res, ctx) {
28
49
  if (req.url !== "/api/setup-status")
29
50
  return false;
30
51
  if (req.method !== "GET") {
@@ -33,9 +54,14 @@ export function handleSetupStatus(req, res, _ctx) {
33
54
  res.end(JSON.stringify({ error: "method_not_allowed" }));
34
55
  return true;
35
56
  }
57
+ const configured = detectConfiguredEmbedder(ctx.stateDir);
58
+ const active = detectCurrentEmbedder();
36
59
  const body = {
37
- currentEmbedder: detectCurrentEmbedder(),
38
- embeddingUrl: process.env["MEGACOMPACT_EMBEDDING_URL"] ?? null,
60
+ currentEmbedder: active,
61
+ configuredEmbedder: configured.embedder,
62
+ configuredUrl: configured.url,
63
+ restartRequired: active !== configured.embedder,
64
+ embeddingUrl: process.env["MEGACOMPACT_EMBEDDING_URL"] ?? configured.url,
39
65
  embedCache: process.env["MEGACOMPACT_EMBED_CACHE"] ?? null,
40
66
  minilm: ["1", "true", "yes"].includes((process.env["MEGACOMPACT_MINILM"] ?? "").trim().toLowerCase()),
41
67
  };
@@ -48,10 +48,11 @@ export function buildCheckpointNodes(db, sessionId, nodes, edges) {
48
48
  let prevId = null;
49
49
  for (const row of rows) {
50
50
  const id = String(row.id);
51
+ const rowSessionId = sessionId || String(row.session_id ?? "");
51
52
  const summary = String(row.summary ?? "");
52
53
  const node = {
53
54
  id,
54
- sessionId,
55
+ sessionId: rowSessionId,
55
56
  label: id,
56
57
  summaryTruncated: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
57
58
  tokenEstimate: Number(row.token_estimate ?? 0),
@@ -107,14 +108,14 @@ export function buildTurnNodes(db, sessionId, nodes, edges) {
107
108
  const existingIds = new Set(nodes.map((n) => n.id));
108
109
  const rows = sessionId
109
110
  ? db
110
- .prepare(`SELECT turn_index, role, pressure_band, ctx_tokens, ctx_percent,
111
+ .prepare(`SELECT turn_index, session_id, role, pressure_band, ctx_tokens, ctx_percent,
111
112
  epoch_id, ended_at
112
113
  FROM turns
113
114
  WHERE session_id = ?
114
115
  ORDER BY turn_index ASC`)
115
116
  .all(sessionId)
116
117
  : db
117
- .prepare(`SELECT turn_index, role, pressure_band, ctx_tokens, ctx_percent,
118
+ .prepare(`SELECT turn_index, session_id, role, pressure_band, ctx_tokens, ctx_percent,
118
119
  epoch_id, ended_at
119
120
  FROM turns
120
121
  ORDER BY turn_index ASC`)
@@ -124,7 +125,8 @@ export function buildTurnNodes(db, sessionId, nodes, edges) {
124
125
  let prevId = null;
125
126
  for (const row of rows) {
126
127
  const ti = Number(row.turn_index);
127
- const nodeId = `turn:${sessionId}:${ti}`;
128
+ const rowSessionId = sessionId || String(row.session_id ?? "");
129
+ const nodeId = `turn:${rowSessionId}:${ti}`;
128
130
  if (existingIds.has(nodeId))
129
131
  continue;
130
132
  existingIds.add(nodeId);
@@ -132,7 +134,7 @@ export function buildTurnNodes(db, sessionId, nodes, edges) {
132
134
  const endedAt = row.ended_at ? Number(row.ended_at) : Date.now();
133
135
  const node = {
134
136
  id: nodeId,
135
- sessionId,
137
+ sessionId: rowSessionId,
136
138
  label: `Turn ${ti}`,
137
139
  summaryTruncated: `Turn ${ti}: ${role}${row.epoch_id ? ` (epoch: ${String(row.epoch_id)})` : ""}`,
138
140
  tokenEstimate: row.ctx_tokens ? Number(row.ctx_tokens) : 0,
@@ -155,26 +157,53 @@ export function buildTurnNodes(db, sessionId, nodes, edges) {
155
157
  // ---------------------------------------------------------------------------
156
158
  // Source B: turn-content nodes (from turns JOIN raw_transcript)
157
159
  // ---------------------------------------------------------------------------
158
- export function buildTurnContentNodes(db, sessionId, nodes, edges) {
160
+ export function buildTurnContentNodes(turnDb, mainDb, sessionId, nodes, edges) {
159
161
  // Local dedup only — DON'T skip ids from Source A (turn-structural). Source A
160
162
  // and Source B share the same node id (turn:<session>:<idx>); Gate 2
161
163
  // (identity_merge) merges them with the richest nodeType winning. Using the
162
164
  // global nodes array here would skip every node (Source A already added them).
163
165
  const seen = new Set();
164
- const rows = sessionId
165
- ? db
166
- .prepare(`SELECT t.turn_index, t.role, t.ended_at, r.content_bytes
167
- FROM turns t
168
- JOIN raw_transcript r ON r.session_id = t.session_id AND r.turn_index = t.turn_index
169
- WHERE t.session_id = ?
170
- ORDER BY t.turn_index ASC`)
166
+ // turns live in turns.db, raw_transcript in the main db — can't JOIN across
167
+ // databases, so query separately and merge in JS.
168
+ const turnRows = sessionId
169
+ ? turnDb
170
+ .prepare(`SELECT turn_index, role, ended_at
171
+ FROM turns
172
+ WHERE session_id = ?
173
+ ORDER BY turn_index ASC`)
171
174
  .all(sessionId)
172
- : db
173
- .prepare(`SELECT t.turn_index, t.role, t.ended_at, r.content_bytes
174
- FROM turns t
175
- JOIN raw_transcript r ON r.session_id = t.session_id AND r.turn_index = t.turn_index
176
- ORDER BY t.turn_index ASC`)
175
+ : turnDb
176
+ .prepare(`SELECT turn_index, role, ended_at
177
+ FROM turns
178
+ ORDER BY turn_index ASC`)
177
179
  .all();
180
+ // Build a map of (session_id, turn_index) → content from raw_transcript.
181
+ const contentMap = new Map();
182
+ try {
183
+ const transcriptRows = sessionId
184
+ ? mainDb
185
+ .prepare(`SELECT turn_index, content_bytes FROM raw_transcript WHERE session_id = ?`)
186
+ .all(sessionId)
187
+ : mainDb
188
+ .prepare(`SELECT turn_index, content_bytes FROM raw_transcript`)
189
+ .all();
190
+ for (const r of transcriptRows) {
191
+ contentMap.set(String(r.turn_index), String(r.content_bytes ?? ""));
192
+ }
193
+ }
194
+ catch {
195
+ // raw_transcript may not exist in the main db (dbMirror off) — return empty.
196
+ return;
197
+ }
198
+ // Merge: only produce nodes where both a turn row AND transcript content exist.
199
+ const rows = turnRows
200
+ .filter((t) => contentMap.has(String(t.turn_index)))
201
+ .map((t) => ({
202
+ turn_index: t.turn_index,
203
+ role: t.role,
204
+ ended_at: t.ended_at,
205
+ content_bytes: contentMap.get(String(t.turn_index)) ?? "",
206
+ }));
178
207
  if (rows.length === 0)
179
208
  return;
180
209
  const turnContentNodes = [];
@@ -206,7 +235,7 @@ export function buildTurnContentNodes(db, sessionId, nodes, edges) {
206
235
  if (turnContentNodes.length > 1) {
207
236
  const embeddings = turnContentNodes.map((n) => {
208
237
  try {
209
- return { id: n.id, vec: getOrComputeEmbedding(db, n.textSnippet) };
238
+ return { id: n.id, vec: getOrComputeEmbedding(mainDb, n.textSnippet) };
210
239
  }
211
240
  catch {
212
241
  return { id: n.id, vec: null };
@@ -229,7 +258,7 @@ export function buildTurnContentNodes(db, sessionId, nodes, edges) {
229
258
  }
230
259
  }
231
260
  }
232
- linkTurnToCheckpointEdges(db, sessionId, turnContentNodes, edges);
261
+ linkTurnToCheckpointEdges(mainDb, sessionId, turnContentNodes, edges);
233
262
  }
234
263
  nodes.push(...turnContentNodes);
235
264
  }
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { getStateDir } from "./store.js";
16
16
  import { openStore } from "./store/sqlite/utils.js";
17
+ import { openTurnStore } from "./store/turns/connection.js";
17
18
  import { Logger } from "./log.js";
18
19
  import { runValidationPipeline } from "./memoryGraph/gates.js";
19
20
  import { buildCheckpointNodes, buildTurnNodes, buildTurnContentNodes, buildMemoryNodes, addRaptorAnnotations, deduplicateEdges, areTurnsEnabled, isTurnContentEnabled, isTurnContentFlaggedOn, areMemoriesEnabled, } from "./memoryGraph/sources.js";
@@ -83,13 +84,22 @@ function buildUnvalidatedGraph(sessionId, stateDir) {
83
84
  log.warn("graph_source_unavailable", { detail: "Cannot open store" });
84
85
  return { nodes: [], edges: [] };
85
86
  }
87
+ // Turns live in the isolated turns.db, not the main sqlite.db. Open it
88
+ // separately so buildTurnNodes/buildTurnContentNodes can query it.
89
+ let turnDb = null;
90
+ try {
91
+ turnDb = openTurnStore(stateDir);
92
+ }
93
+ catch {
94
+ log.warn("graph_source_unavailable", { detail: "Cannot open turns.db" });
95
+ }
86
96
  try {
87
97
  // Source: checkpoints (always on)
88
98
  buildCheckpointNodes(db, sessionId, nodes, edges);
89
- // Source A: turns (structural, metadata-only)
90
- if (areTurnsEnabled()) {
99
+ // Source A: turns (structural, metadata-only) — from turns.db, not main db
100
+ if (areTurnsEnabled() && turnDb) {
91
101
  try {
92
- buildTurnNodes(db, sessionId, nodes, edges);
102
+ buildTurnNodes(turnDb, sessionId, nodes, edges);
93
103
  }
94
104
  catch (err) {
95
105
  log.warn("graph_source_unavailable", {
@@ -99,9 +109,11 @@ function buildUnvalidatedGraph(sessionId, stateDir) {
99
109
  }
100
110
  }
101
111
  // Source B: turn content (raw_transcript join, embedding-based)
102
- if (isTurnContentEnabled()) {
112
+ // raw_transcript is in the main db, but turns are in turns.db — the JOIN
113
+ // can't span databases, so we pass both and merge in JS.
114
+ if (isTurnContentEnabled() && turnDb) {
103
115
  try {
104
- buildTurnContentNodes(db, sessionId, nodes, edges);
116
+ buildTurnContentNodes(turnDb, db, sessionId, nodes, edges);
105
117
  }
106
118
  catch (err) {
107
119
  log.warn("graph_source_unavailable", {
@@ -1,7 +1,23 @@
1
1
  /** Schema version stamp (written once into turns_meta). */
2
- const SCHEMA_VERSION = 1;
2
+ const SCHEMA_VERSION = 2;
3
3
  /** Create the unified turn-store schema if absent. Idempotent. */
4
4
  export function initTurnSchema(db) {
5
+ // Migration: v1 had a pressure_band CHECK constraint that only allowed
6
+ // 'green','yellow','red' — but the runtime sends 'low','medium','high',
7
+ // 'ultra','mega'. Every INSERT failed silently. Since the constraint
8
+ // blocked ALL writes, the turns table is guaranteed empty — safe to
9
+ // drop and recreate with the widened constraint.
10
+ try {
11
+ const tableSql = db
12
+ .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='turns'")
13
+ .get();
14
+ if (tableSql?.sql && tableSql.sql.includes("'green'") && !tableSql.sql.includes("'low'")) {
15
+ db.exec("DROP TABLE IF EXISTS turns");
16
+ }
17
+ }
18
+ catch {
19
+ /* non-fatal: table may not exist yet */
20
+ }
5
21
  // ── Contract tables (master's SqliteTurnStore shape) ──────────────────
6
22
  db.exec(`
7
23
  CREATE TABLE IF NOT EXISTS turns_meta (
@@ -22,7 +38,7 @@ export function initTurnSchema(db) {
22
38
  ended_at INTEGER NOT NULL,
23
39
  ctx_tokens INTEGER,
24
40
  ctx_percent REAL,
25
- pressure_band TEXT CHECK(pressure_band IS NULL OR pressure_band IN ('green','yellow','red')),
41
+ pressure_band TEXT CHECK(pressure_band IS NULL OR pressure_band IN ('low','medium','high','ultra','mega','green','yellow','red')),
26
42
  model TEXT,
27
43
  epoch_id TEXT,
28
44
  UNIQUE(conversation_id, turn_index)
@@ -1,2 +1,2 @@
1
- import{r as o,j as n}from"./index-C9dTXvDQ.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-Cwg_Ab8R.js.map
1
+ import{r as o,j as n}from"./index-BEeaI9uo.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-Cntu89Xw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementTiles-Cwg_Ab8R.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-Cntu89Xw.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-C9dTXvDQ.js";import{A as v}from"./AchievementTiles-Cwg_Ab8R.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-Dk32-4Ha.js.map
1
+ import{r as t,n as m,j as s}from"./index-BEeaI9uo.js";import{A as v}from"./AchievementTiles-Cntu89Xw.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-4ckY9z4n.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementsTab-Dk32-4Ha.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-4ckY9z4n.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,2 +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-C9dTXvDQ.js";import{C as L,T as A}from"./TimeSavedCard-B5Lw6fTp.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-B9zR8UgD.js.map
1
+ import{j as e,r as h,u as j,k as R,l as P,i as E,c as $}from"./index-BEeaI9uo.js";import{C as L,T as A}from"./TimeSavedCard-BBwvb4cC.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-BfGJkiaf.js.map