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.
- package/dist/extensions/dashboard-server/gather-debug.js +63 -0
- package/dist/extensions/dashboard-server/routes-game.js +6 -1
- package/dist/extensions/dashboard-server/routes-setup.js +30 -4
- package/dist/src/memoryGraph/sources.js +49 -20
- package/dist/src/memoryGraph.js +17 -5
- package/dist/src/store/turns/schema.js +18 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cwg_Ab8R.js → AchievementTiles-Cntu89Xw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-Cwg_Ab8R.js.map → AchievementTiles-Cntu89Xw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dk32-4Ha.js → AchievementsTab-4ckY9z4n.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dk32-4Ha.js.map → AchievementsTab-4ckY9z4n.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js → CacheTab-BfGJkiaf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js.map → CacheTab-BfGJkiaf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DYRl3mJZ.js → ConfigTab-DV08rHoV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DYRl3mJZ.js.map → ConfigTab-DV08rHoV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-r_zXcrmZ.js → EventsTab-DHytZ0xs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-r_zXcrmZ.js.map → EventsTab-DHytZ0xs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-DjwA_Q1v.js → GameTab-Ofpoz-6s.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-DjwA_Q1v.js.map → GameTab-Ofpoz-6s.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-BjH73kQr.js → HealthTab-B0klAxD8.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-BjH73kQr.js.map → HealthTab-B0klAxD8.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DzeSq9vK.js → MaintenanceTab-CHKh3KPI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DzeSq9vK.js.map → MaintenanceTab-CHKh3KPI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BU9iM3ir.js → MemoryMapTab-B6FLQ-32.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BU9iM3ir.js.map → MemoryMapTab-B6FLQ-32.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-lMVodOwY.js → MetricsTab-DvOjTDkW.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-lMVodOwY.js.map → MetricsTab-DvOjTDkW.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-D-laa6CW.js → OverviewTab-CqbONEwi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-D-laa6CW.js.map → OverviewTab-CqbONEwi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-5Pn2cOOO.js → ReposTab-sc1GLn7i.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-5Pn2cOOO.js.map → ReposTab-sc1GLn7i.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-Dtdmvl2C.js → SessionsTab-C5gB45oc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-Dtdmvl2C.js.map → SessionsTab-C5gB45oc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B5Lw6fTp.js → TimeSavedCard-BBwvb4cC.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B5Lw6fTp.js.map → TimeSavedCard-BBwvb4cC.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-Ds1IHhPw.js → TopicsTab-BsCf512_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-Ds1IHhPw.js.map → TopicsTab-BsCf512_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CG2RDRmh.js → TurnsTab-C3ti7NB5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CG2RDRmh.js.map → TurnsTab-C3ti7NB5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-C9dTXvDQ.js → index-BEeaI9uo.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-C9dTXvDQ.js.map → index-BEeaI9uo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-B6l8O-yi.js → useSSE-BMSSO9wB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-B6l8O-yi.js.map → useSSE-BMSSO9wB.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +19 -0
- package/extensions/dashboard-server/api-contracts/maintenance.ts +1 -0
- package/extensions/dashboard-server/api-contracts/setup.ts +7 -1
- package/extensions/dashboard-server/gather-debug.ts +63 -0
- package/extensions/dashboard-server/routes-game.ts +6 -1
- package/extensions/dashboard-server/routes-setup.ts +28 -4
- package/package.json +1 -1
- package/src/memoryGraph/sources.ts +56 -20
- package/src/memoryGraph.ts +17 -5
- package/src/store/turns/schema.ts +18 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-rKGgWpI0.js +0 -2
- 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
|
-
|
|
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
|
-
|
|
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:
|
|
38
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
:
|
|
173
|
-
.prepare(`SELECT
|
|
174
|
-
FROM turns
|
|
175
|
-
|
|
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(
|
|
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(
|
|
261
|
+
linkTurnToCheckpointEdges(mainDb, sessionId, turnContentNodes, edges);
|
|
233
262
|
}
|
|
234
263
|
nodes.push(...turnContentNodes);
|
|
235
264
|
}
|
package/dist/src/memoryGraph.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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 =
|
|
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-
|
|
2
|
-
//# sourceMappingURL=AchievementTiles-
|
|
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-
|
|
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-
|
|
2
|
-
//# sourceMappingURL=AchievementsTab-
|
|
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-
|
|
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"}
|
package/extensions/dashboard-client/dist/assets/{CacheTab-B9zR8UgD.js → CacheTab-BfGJkiaf.js}
RENAMED
|
@@ -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-
|
|
2
|
-
//# sourceMappingURL=CacheTab-
|
|
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
|