pi-mega-compact 0.12.0 → 0.12.2
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-health.js +44 -0
- package/dist/extensions/dashboard-server/routes-setup.js +30 -4
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/extensions/mega-events/agent-handlers.js +27 -1
- package/dist/extensions/mega-events/health-handler.js +19 -2
- package/dist/src/memoryGraph/sources.js +42 -15
- package/dist/src/memoryGraph.js +17 -5
- package/dist/src/store/sqlite/meta.js +11 -0
- package/dist/src/store/turns/schema.js +18 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DeQ0qnBX.js → AchievementTiles-Cntu89Xw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DeQ0qnBX.js.map → AchievementTiles-Cntu89Xw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dtk_RJfs.js → AchievementsTab-4ckY9z4n.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Dtk_RJfs.js.map → AchievementsTab-4ckY9z4n.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-kWcQt3PI.js → CacheTab-BfGJkiaf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-kWcQt3PI.js.map → CacheTab-BfGJkiaf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-UFlo73fR.js → ConfigTab-DV08rHoV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-UFlo73fR.js.map → ConfigTab-DV08rHoV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-BENO2N0y.js → EventsTab-DHytZ0xs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-BENO2N0y.js.map → EventsTab-DHytZ0xs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-BCJavOML.js → GameTab-Ofpoz-6s.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-BCJavOML.js.map → GameTab-Ofpoz-6s.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/HealthTab-B0klAxD8.js +2 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-B0klAxD8.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-CHKh3KPI.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-CHKh3KPI.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-w5BZP3i-.js → MemoryMapTab-B6FLQ-32.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-w5BZP3i-.js.map → MemoryMapTab-B6FLQ-32.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-BBCXa3xS.js → MetricsTab-DvOjTDkW.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-BBCXa3xS.js.map → MetricsTab-DvOjTDkW.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CzABMSAQ.js → OverviewTab-CqbONEwi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CzABMSAQ.js.map → OverviewTab-CqbONEwi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DFiM_Ubg.js → ReposTab-sc1GLn7i.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DFiM_Ubg.js.map → ReposTab-sc1GLn7i.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BtW4bees.js → SessionsTab-C5gB45oc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BtW4bees.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-CC13EXij.js → TimeSavedCard-BBwvb4cC.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CC13EXij.js.map → TimeSavedCard-BBwvb4cC.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BrTB8uEK.js → TopicsTab-BsCf512_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BrTB8uEK.js.map → TopicsTab-BsCf512_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BJK7FIHu.js → TurnsTab-C3ti7NB5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BJK7FIHu.js.map → TurnsTab-C3ti7NB5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js +2 -0
- package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-B8k19B9g.js → index-BEeaI9uo.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-B8k19B9g.js.map → index-BEeaI9uo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-CWBB4Me7.js → useSSE-BMSSO9wB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-CWBB4Me7.js.map → useSSE-BMSSO9wB.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/health.ts +16 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +91 -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-health.ts +51 -0
- package/extensions/dashboard-server/routes-setup.ts +28 -4
- package/extensions/dashboard-server/routes.ts +1 -1
- package/extensions/dashboard-server/server.ts +2 -0
- package/extensions/mega-events/agent-handlers.ts +26 -1
- package/extensions/mega-events/health-handler.ts +31 -3
- package/package.json +1 -1
- package/src/memoryGraph/sources.ts +49 -15
- package/src/memoryGraph.ts +17 -5
- package/src/store/sqlite/meta.ts +16 -0
- package/src/store/turns/schema.ts +18 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-C7iw7rhx.js +0 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-C7iw7rhx.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-C0rdAMWc.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-C0rdAMWc.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-Duy1LJhg.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-Duy1LJhg.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);
|
|
@@ -79,3 +79,47 @@ export function handleCachePoison(req, res, ctx) {
|
|
|
79
79
|
}
|
|
80
80
|
return true;
|
|
81
81
|
}
|
|
82
|
+
/** GET/PUT /api/context-health/settings — read or toggle mitigation. */
|
|
83
|
+
export function handleHealthSettings(req, res, ctx) {
|
|
84
|
+
if (!req.url?.startsWith("/api/context-health/settings"))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
const pfReq = createRequire(import.meta.url);
|
|
88
|
+
const { getHealthMitigate, setHealthMitigate } = pfReq("../../src/store/sqlite.js");
|
|
89
|
+
if (req.method === "GET") {
|
|
90
|
+
const mitigate = getHealthMitigate(ctx.stateDir);
|
|
91
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
92
|
+
res.end(JSON.stringify({ mitigate }));
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (req.method === "PUT") {
|
|
96
|
+
let body = "";
|
|
97
|
+
req.on("data", (c) => (body += c));
|
|
98
|
+
req.on("end", () => {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(body);
|
|
101
|
+
if (typeof parsed.mitigate !== "boolean") {
|
|
102
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
103
|
+
res.end(JSON.stringify({ error: "mitigate must be boolean" }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
setHealthMitigate(parsed.mitigate, ctx.stateDir);
|
|
107
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
108
|
+
res.end(JSON.stringify({ mitigate: parsed.mitigate }));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
112
|
+
res.end(JSON.stringify({ error: "invalid_json" }));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
118
|
+
res.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
122
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
@@ -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
|
};
|
|
@@ -16,4 +16,4 @@ export { handleMemoryStatus } from "./routes-memory.js";
|
|
|
16
16
|
export { handleSetupStatus, handleSetupDetect, handleSetupConfigure } from "./routes-setup.js";
|
|
17
17
|
export { handleMemoryMap } from "./routes-memory-map.js";
|
|
18
18
|
export { handleCacheStripes } from "./routes-cache.js";
|
|
19
|
-
export { handleContextHealth, handleCachePoison } from "./routes-health.js";
|
|
19
|
+
export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
|
|
@@ -12,7 +12,7 @@ import { join, dirname } from "node:path";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
14
|
import { log, setLogPath, setDashboardServerVersion } from "./state.js";
|
|
15
|
-
import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleContextHealth, handleCachePoison, handleStatic, } from "./routes.js";
|
|
15
|
+
import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleContextHealth, handleCachePoison, handleHealthSettings, handleStatic, } from "./routes.js";
|
|
16
16
|
export async function launchDashboardServer(stateDir) {
|
|
17
17
|
// Our own package version — exposed at /api/version so the launcher can
|
|
18
18
|
// detect a stale server (started by an older build) and replace it on
|
|
@@ -211,6 +211,8 @@ export async function launchDashboardServer(stateDir) {
|
|
|
211
211
|
return;
|
|
212
212
|
if (handleCachePoison(req, res, ctx))
|
|
213
213
|
return;
|
|
214
|
+
if (handleHealthSettings(req, res, ctx))
|
|
215
|
+
return;
|
|
214
216
|
handleStatic(req, res, ctx);
|
|
215
217
|
});
|
|
216
218
|
// Bind base + range are env-configurable so tests can use a private,
|
|
@@ -785,6 +785,32 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
785
785
|
}
|
|
786
786
|
}
|
|
787
787
|
// v0.12: Context Health — compute + persist health score. Non-fatal.
|
|
788
|
-
|
|
788
|
+
// Mitigation signals are acted on here (ctx is in scope at turn_end).
|
|
789
|
+
const healthSignal = handleTurnEndHealth(event, runtime, config);
|
|
790
|
+
if (healthSignal.forceCompact) {
|
|
791
|
+
runtime.logger?.info("health_mitigate_compact", {
|
|
792
|
+
composite: healthSignal.composite,
|
|
793
|
+
turnIndex: event.turnIndex,
|
|
794
|
+
});
|
|
795
|
+
try {
|
|
796
|
+
// Force a compaction to flush degraded context. Same race-guarded
|
|
797
|
+
// deferred path as the critical-over escape hatch above.
|
|
798
|
+
if (!piCompactWouldNoop(ctx)) {
|
|
799
|
+
ctx.compact();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
/* non-fatal: mitigation never breaks the agent loop */
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (healthSignal.breakPrefix) {
|
|
807
|
+
runtime.logger?.info("health_mitigate_prefix_break", {
|
|
808
|
+
turnIndex: event.turnIndex,
|
|
809
|
+
});
|
|
810
|
+
// Invalidate the cached trim so the next context event regenerates
|
|
811
|
+
// from scratch — the prefix change forces a cache miss, bypassing
|
|
812
|
+
// the corrupted KV state.
|
|
813
|
+
runtime.trimCache = null;
|
|
814
|
+
}
|
|
789
815
|
});
|
|
790
816
|
}
|
|
@@ -14,6 +14,7 @@ import { computeTopicDrift, computeErrorEscalation, computePrefixInstability, co
|
|
|
14
14
|
import { computePrefixHash, checkPrefixHash, evaluateCachePoison, } from "../../src/contextHealth/cachePoison.js";
|
|
15
15
|
import { computeHealthScore, recordContextHealth, } from "../../src/contextHealth.js";
|
|
16
16
|
import { recordCachePoisonEvent, } from "../../src/store/sqlite/context-health.js";
|
|
17
|
+
import { getHealthMitigate } from "../../src/store/sqlite/meta.js";
|
|
17
18
|
const RING_MAX = 5;
|
|
18
19
|
/** Extract assistant text from the turn_end event message. */
|
|
19
20
|
function extractAssistantText(event) {
|
|
@@ -40,10 +41,12 @@ function extractMessageTexts(event) {
|
|
|
40
41
|
}
|
|
41
42
|
return [];
|
|
42
43
|
}
|
|
43
|
-
/** Handle turn_end: compute + persist context health. Non-fatal.
|
|
44
|
+
/** Handle turn_end: compute + persist context health. Non-fatal.
|
|
45
|
+
* Returns mitigation signals for the caller to act on (agent-handlers has ctx). */
|
|
44
46
|
export function handleTurnEndHealth(event, runtime, config) {
|
|
47
|
+
const noSignal = { forceCompact: false, breakPrefix: false, composite: 1 };
|
|
45
48
|
if (!config.contextHealth)
|
|
46
|
-
return;
|
|
49
|
+
return noSignal;
|
|
47
50
|
try {
|
|
48
51
|
const text = extractAssistantText(event);
|
|
49
52
|
const embedder = defaultEmbedder();
|
|
@@ -138,6 +141,10 @@ export function handleTurnEndHealth(event, runtime, config) {
|
|
|
138
141
|
cachePoison,
|
|
139
142
|
turnIndex: event.turnIndex,
|
|
140
143
|
});
|
|
144
|
+
// Mitigation: check the runtime-toggleable flag (meta table), not just
|
|
145
|
+
// the env var. The dashboard Maintenance tab toggles this at runtime.
|
|
146
|
+
const mitigate = config.contextHealthMitigate ||
|
|
147
|
+
getHealthMitigate(runtime.currentStateDir);
|
|
141
148
|
// Update ring buffers
|
|
142
149
|
runtime.recentTurnEmbeddings.push(emb);
|
|
143
150
|
if (runtime.recentTurnEmbeddings.length > RING_MAX) {
|
|
@@ -147,10 +154,20 @@ export function handleTurnEndHealth(event, runtime, config) {
|
|
|
147
154
|
if (runtime.recentErrorCategories.length > RING_MAX) {
|
|
148
155
|
runtime.recentErrorCategories.shift();
|
|
149
156
|
}
|
|
157
|
+
// Return mitigation signals so agent-handlers.ts (which has ctx) can act.
|
|
158
|
+
if (mitigate) {
|
|
159
|
+
return {
|
|
160
|
+
forceCompact: composite < 0.4,
|
|
161
|
+
breakPrefix: cachePoison < 0.3,
|
|
162
|
+
composite,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return { forceCompact: false, breakPrefix: false, composite };
|
|
150
166
|
}
|
|
151
167
|
catch (e) {
|
|
152
168
|
runtime.logger?.error("context_health_failed", {
|
|
153
169
|
error: String(e instanceof Error ? e.message : e),
|
|
154
170
|
});
|
|
171
|
+
return noSignal;
|
|
155
172
|
}
|
|
156
173
|
}
|
|
@@ -155,26 +155,53 @@ export function buildTurnNodes(db, sessionId, nodes, edges) {
|
|
|
155
155
|
// ---------------------------------------------------------------------------
|
|
156
156
|
// Source B: turn-content nodes (from turns JOIN raw_transcript)
|
|
157
157
|
// ---------------------------------------------------------------------------
|
|
158
|
-
export function buildTurnContentNodes(
|
|
158
|
+
export function buildTurnContentNodes(turnDb, mainDb, sessionId, nodes, edges) {
|
|
159
159
|
// Local dedup only — DON'T skip ids from Source A (turn-structural). Source A
|
|
160
160
|
// and Source B share the same node id (turn:<session>:<idx>); Gate 2
|
|
161
161
|
// (identity_merge) merges them with the richest nodeType winning. Using the
|
|
162
162
|
// global nodes array here would skip every node (Source A already added them).
|
|
163
163
|
const seen = new Set();
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
164
|
+
// turns live in turns.db, raw_transcript in the main db — can't JOIN across
|
|
165
|
+
// databases, so query separately and merge in JS.
|
|
166
|
+
const turnRows = sessionId
|
|
167
|
+
? turnDb
|
|
168
|
+
.prepare(`SELECT turn_index, role, ended_at
|
|
169
|
+
FROM turns
|
|
170
|
+
WHERE session_id = ?
|
|
171
|
+
ORDER BY turn_index ASC`)
|
|
171
172
|
.all(sessionId)
|
|
172
|
-
:
|
|
173
|
-
.prepare(`SELECT
|
|
174
|
-
FROM turns
|
|
175
|
-
|
|
176
|
-
ORDER BY t.turn_index ASC`)
|
|
173
|
+
: turnDb
|
|
174
|
+
.prepare(`SELECT turn_index, role, ended_at
|
|
175
|
+
FROM turns
|
|
176
|
+
ORDER BY turn_index ASC`)
|
|
177
177
|
.all();
|
|
178
|
+
// Build a map of (session_id, turn_index) → content from raw_transcript.
|
|
179
|
+
const contentMap = new Map();
|
|
180
|
+
try {
|
|
181
|
+
const transcriptRows = sessionId
|
|
182
|
+
? mainDb
|
|
183
|
+
.prepare(`SELECT turn_index, content_bytes FROM raw_transcript WHERE session_id = ?`)
|
|
184
|
+
.all(sessionId)
|
|
185
|
+
: mainDb
|
|
186
|
+
.prepare(`SELECT turn_index, content_bytes FROM raw_transcript`)
|
|
187
|
+
.all();
|
|
188
|
+
for (const r of transcriptRows) {
|
|
189
|
+
contentMap.set(String(r.turn_index), String(r.content_bytes ?? ""));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// raw_transcript may not exist in the main db (dbMirror off) — return empty.
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// Merge: only produce nodes where both a turn row AND transcript content exist.
|
|
197
|
+
const rows = turnRows
|
|
198
|
+
.filter((t) => contentMap.has(String(t.turn_index)))
|
|
199
|
+
.map((t) => ({
|
|
200
|
+
turn_index: t.turn_index,
|
|
201
|
+
role: t.role,
|
|
202
|
+
ended_at: t.ended_at,
|
|
203
|
+
content_bytes: contentMap.get(String(t.turn_index)) ?? "",
|
|
204
|
+
}));
|
|
178
205
|
if (rows.length === 0)
|
|
179
206
|
return;
|
|
180
207
|
const turnContentNodes = [];
|
|
@@ -206,7 +233,7 @@ export function buildTurnContentNodes(db, sessionId, nodes, edges) {
|
|
|
206
233
|
if (turnContentNodes.length > 1) {
|
|
207
234
|
const embeddings = turnContentNodes.map((n) => {
|
|
208
235
|
try {
|
|
209
|
-
return { id: n.id, vec: getOrComputeEmbedding(
|
|
236
|
+
return { id: n.id, vec: getOrComputeEmbedding(mainDb, n.textSnippet) };
|
|
210
237
|
}
|
|
211
238
|
catch {
|
|
212
239
|
return { id: n.id, vec: null };
|
|
@@ -229,7 +256,7 @@ export function buildTurnContentNodes(db, sessionId, nodes, edges) {
|
|
|
229
256
|
}
|
|
230
257
|
}
|
|
231
258
|
}
|
|
232
|
-
linkTurnToCheckpointEdges(
|
|
259
|
+
linkTurnToCheckpointEdges(mainDb, sessionId, turnContentNodes, edges);
|
|
233
260
|
}
|
|
234
261
|
nodes.push(...turnContentNodes);
|
|
235
262
|
}
|
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", {
|
|
@@ -57,6 +57,17 @@ export function bumpDedupStats(deduped, stateDir = getStateDir()) {
|
|
|
57
57
|
if (deduped)
|
|
58
58
|
incMeta("deduped", 1, stateDir);
|
|
59
59
|
}
|
|
60
|
+
// --- Context health mitigation (runtime-toggleable via dashboard) ----------
|
|
61
|
+
/** Read the context_health_mitigate flag from meta. Returns false if unset. */
|
|
62
|
+
export function getHealthMitigate(stateDir = getStateDir()) {
|
|
63
|
+
return getMeta("context_health_mitigate", stateDir) === "true";
|
|
64
|
+
}
|
|
65
|
+
/** Set the context_health_mitigate flag in meta (runtime-toggleable). */
|
|
66
|
+
export function setHealthMitigate(enabled, stateDir = getStateDir()) {
|
|
67
|
+
const db = openStore(stateDir);
|
|
68
|
+
db.prepare(`INSERT INTO meta(key, value) VALUES('context_health_mitigate', ?)
|
|
69
|
+
ON CONFLICT(key) DO UPDATE SET value = ?`).run(enabled ? "true" : "false", enabled ? "true" : "false");
|
|
70
|
+
}
|
|
60
71
|
// --- Live dashboard counters (schemaless meta key/value — NO migration) -----
|
|
61
72
|
// These reuse the private `incMeta` atomically-incrementing integer counter so
|
|
62
73
|
// all cumulative tallies live in the same `meta` table as tokens_saved etc.
|
|
@@ -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-kWcQt3PI.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
|