pi-mega-compact 0.11.1 → 0.11.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/api-contracts/endpoints.js +19 -0
- package/dist/extensions/dashboard-server/api-contracts/maintenance.js +7 -0
- package/dist/extensions/dashboard-server/api-contracts.test.js +1 -1
- package/dist/extensions/dashboard-server/routes-maintenance.js +293 -0
- package/dist/extensions/dashboard-server/routes.js +1 -0
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/src/store/sqlite/maintenance.js +20 -1
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DZp_xQOW.js → AchievementTiles-Cml6YC8w.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DZp_xQOW.js.map → AchievementTiles-Cml6YC8w.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BzFHpAHM.js → AchievementsTab-B8NqDt7I.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BzFHpAHM.js.map → AchievementsTab-B8NqDt7I.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-DEQbTq2T.js → CacheTab-BCLzVL4S.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-DEQbTq2T.js.map → CacheTab-BCLzVL4S.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DQ4FG33J.js → ConfigTab-CBYvp2mV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DQ4FG33J.js.map → ConfigTab-CBYvp2mV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-D5HoEVYo.js → EventsTab-DlTJqiDm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-D5HoEVYo.js.map → EventsTab-DlTJqiDm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-Jzsc4yeY.js → GameTab-DkGTqKJW.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-Jzsc4yeY.js.map → GameTab-DkGTqKJW.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Cw43zmUx.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-Cw43zmUx.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-D_XnsEYX.js → MetricsTab-BFo7pKM-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-D_XnsEYX.js.map → MetricsTab-BFo7pKM-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BMREvAth.js → OverviewTab-CClCUd3-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BMREvAth.js.map → OverviewTab-CClCUd3-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DuyhOktG.js → ReposTab-_3R4RSLZ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DuyhOktG.js.map → ReposTab-_3R4RSLZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-mz_vz8i2.js → SessionsTab-CCYd-kSz.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-mz_vz8i2.js.map → SessionsTab-CCYd-kSz.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B8AIjNTY.js → TimeSavedCard-0TqsrY6y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B8AIjNTY.js.map → TimeSavedCard-0TqsrY6y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DKuct4oD.js → TopicsTab-DRLJmVv0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-DKuct4oD.js.map → TopicsTab-DRLJmVv0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-11MGLxY8.js → TurnsTab-CLNM0T5R.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-11MGLxY8.js.map → TurnsTab-CLNM0T5R.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CqLN7STl.js → index-BcoSz-L4.js} +10 -10
- package/extensions/dashboard-client/dist/assets/{index-CqLN7STl.js.map → index-BcoSz-L4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-CXrQSbVY.js → useSSE-Cezlih5V.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-CXrQSbVY.js.map → useSSE-Cezlih5V.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/App.tsx +5 -1
- package/extensions/dashboard-client/src/api/client.ts +23 -0
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +293 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -0
- package/extensions/dashboard-server/api-contracts/index.ts +12 -0
- package/extensions/dashboard-server/api-contracts/maintenance.ts +84 -0
- package/extensions/dashboard-server/api-contracts.test.ts +1 -1
- package/extensions/dashboard-server/api-contracts.ts +9 -0
- package/extensions/dashboard-server/routes-maintenance.ts +345 -0
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +1 -1
- package/src/store/sqlite/maintenance.ts +20 -1
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/routes-maintenance.ts — S49B Maintenance tab API routes.
|
|
3
|
+
*
|
|
4
|
+
* Handlers for:
|
|
5
|
+
* GET /api/maintenance — DB stats (table row counts + storage)
|
|
6
|
+
* GET /api/maintenance/schema-health — SCHEMA_VERSION + integrity audit
|
|
7
|
+
* POST /api/maintenance/action — Trigger a maintenance action
|
|
8
|
+
*
|
|
9
|
+
* All pi-agnostic; lazy-requires maintenance primitives to avoid a top-level
|
|
10
|
+
* import of the store from the dashboard server process. PREVENT-PI-004: local
|
|
11
|
+
* SQLite only; zero network calls.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createRequire } from "node:module";
|
|
15
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
16
|
+
|
|
17
|
+
import type { RouteContext } from "./routes-core.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Lazy imports (via createRequire)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const lazyMaintenance = (() => {
|
|
24
|
+
let mod: typeof import("../../src/store/sqlite/maintenance.js") | null = null;
|
|
25
|
+
return () => {
|
|
26
|
+
if (!mod)
|
|
27
|
+
mod = createRequire(import.meta.url)(
|
|
28
|
+
"../../src/store/sqlite/maintenance.js",
|
|
29
|
+
) as typeof import("../../src/store/sqlite/maintenance.js");
|
|
30
|
+
return mod;
|
|
31
|
+
};
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
const lazyStoreUtils = (() => {
|
|
35
|
+
let mod: typeof import("../../src/store/sqlite/utils.js") | null = null;
|
|
36
|
+
return () => {
|
|
37
|
+
if (!mod)
|
|
38
|
+
mod = createRequire(import.meta.url)(
|
|
39
|
+
"../../src/store/sqlite/utils.js",
|
|
40
|
+
) as typeof import("../../src/store/sqlite/utils.js");
|
|
41
|
+
return mod;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Types for JSON deserialisation
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
interface MaintenanceActionBody {
|
|
50
|
+
action: string;
|
|
51
|
+
daysOld?: number | string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// GET /api/maintenance and sub-paths
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
export function handleMaintenance(
|
|
59
|
+
req: IncomingMessage,
|
|
60
|
+
res: ServerResponse,
|
|
61
|
+
ctx: RouteContext,
|
|
62
|
+
): boolean {
|
|
63
|
+
const url = req.url ?? "";
|
|
64
|
+
if (!url.startsWith("/api/maintenance")) return false;
|
|
65
|
+
|
|
66
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
67
|
+
|
|
68
|
+
// ── GET /api/maintenance/schema-health ────────────────────────────
|
|
69
|
+
if (method === "GET" && url === "/api/maintenance/schema-health") {
|
|
70
|
+
sendSchemaHealth(res, ctx);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── POST /api/maintenance/action ──────────────────────────────────
|
|
75
|
+
if (method === "POST" && url === "/api/maintenance/action") {
|
|
76
|
+
handleMaintenanceAction(req, res, ctx);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── GET /api/maintenance ──────────────────────────────────────────
|
|
81
|
+
if (method === "GET" && url === "/api/maintenance") {
|
|
82
|
+
sendDbStats(res, ctx);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Unknown sub-path ──────────────────────────────────────────────
|
|
87
|
+
sendJson(res, 404, { error: "not found" });
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// GET /api/maintenance — DB stats
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
function sendDbStats(res: ServerResponse, ctx: RouteContext): void {
|
|
96
|
+
try {
|
|
97
|
+
const { getDbStats } = lazyMaintenance();
|
|
98
|
+
const raw = getDbStats(ctx.stateDir);
|
|
99
|
+
|
|
100
|
+
const tables = Object.entries(raw.tableCounts).map(([table, rowCount]) => ({
|
|
101
|
+
table,
|
|
102
|
+
rowCount,
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
sendJson(res, 200, {
|
|
106
|
+
type: "db-stats" as const,
|
|
107
|
+
tables,
|
|
108
|
+
storage: {
|
|
109
|
+
files: {
|
|
110
|
+
dbBytes: raw.dbBytes,
|
|
111
|
+
walBytes: raw.walBytes,
|
|
112
|
+
shmBytes: raw.shmBytes,
|
|
113
|
+
},
|
|
114
|
+
pageSize: raw.pageSize,
|
|
115
|
+
pageCount: raw.pageCount,
|
|
116
|
+
freelistPages: raw.freelistPages,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
} catch (err) {
|
|
120
|
+
sendJson(res, 500, { error: (err as Error).message });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// GET /api/maintenance/schema-health — schema version + integrity audit
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
function sendSchemaHealth(res: ServerResponse, ctx: RouteContext): void {
|
|
129
|
+
try {
|
|
130
|
+
const { integrityCheck } = lazyMaintenance();
|
|
131
|
+
const integrity = integrityCheck(ctx.stateDir);
|
|
132
|
+
|
|
133
|
+
const { openStore } = lazyStoreUtils();
|
|
134
|
+
const db = openStore(ctx.stateDir);
|
|
135
|
+
|
|
136
|
+
// SCHEMA_VERSION from meta table
|
|
137
|
+
const metaRow = db
|
|
138
|
+
.prepare("SELECT value FROM meta WHERE key = ?")
|
|
139
|
+
.get("schema_version") as { value: string } | undefined;
|
|
140
|
+
const schemaVersion = metaRow ? Number(metaRow.value) : 0;
|
|
141
|
+
|
|
142
|
+
// PRAGMA foreign_key_check
|
|
143
|
+
const fkRows = db
|
|
144
|
+
.prepare("PRAGMA foreign_key_check")
|
|
145
|
+
.all() as Array<Record<string, unknown>>;
|
|
146
|
+
const fkCheck = fkRows.map((r) => JSON.stringify(r));
|
|
147
|
+
|
|
148
|
+
// Per-column audit: every ensureColumn migration column declared in
|
|
149
|
+
// schema.ts, checked via PRAGMA table_info (read-only).
|
|
150
|
+
const expectedColumns: Array<{
|
|
151
|
+
table: string;
|
|
152
|
+
column: string;
|
|
153
|
+
decl: string;
|
|
154
|
+
}> = [
|
|
155
|
+
{ table: "context_chunks", column: "original_token_estimate", decl: "INTEGER" },
|
|
156
|
+
{ table: "raw_transcript", column: "content_ref", decl: "TEXT" },
|
|
157
|
+
{ table: "memories", column: "category", decl: "TEXT" },
|
|
158
|
+
{ table: "memories", column: "target", decl: "TEXT" },
|
|
159
|
+
{ table: "memories", column: "last_referenced", decl: "INTEGER" },
|
|
160
|
+
{ table: "memories", column: "source_turn", decl: "INTEGER" },
|
|
161
|
+
{ table: "raptor_nodes", column: "built_at", decl: "INTEGER" },
|
|
162
|
+
{ table: "raw_transcript", column: "turn_index", decl: "INTEGER" },
|
|
163
|
+
{ table: "session_state", column: "conversation_id", decl: "TEXT" },
|
|
164
|
+
{ table: "session_state", column: "last_turn_id", decl: "INTEGER" },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const columns = expectedColumns.map((ec) => {
|
|
168
|
+
const cols = db
|
|
169
|
+
.prepare(`PRAGMA table_info(${ec.table})`)
|
|
170
|
+
.all() as Array<{ name: string }>;
|
|
171
|
+
const present = cols.some((c) => c.name === ec.column);
|
|
172
|
+
return {
|
|
173
|
+
table: ec.table,
|
|
174
|
+
column: ec.column,
|
|
175
|
+
present,
|
|
176
|
+
expectedDecl: ec.decl,
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const healthy =
|
|
181
|
+
integrity.length === 1 &&
|
|
182
|
+
integrity[0] === "ok" &&
|
|
183
|
+
columns.every((c) => c.present);
|
|
184
|
+
|
|
185
|
+
sendJson(res, 200, {
|
|
186
|
+
type: "schema-health" as const,
|
|
187
|
+
schemaVersion,
|
|
188
|
+
integrity,
|
|
189
|
+
fkCheck,
|
|
190
|
+
columns,
|
|
191
|
+
healthy,
|
|
192
|
+
});
|
|
193
|
+
} catch (err) {
|
|
194
|
+
sendJson(res, 500, { error: (err as Error).message });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// POST /api/maintenance/action — Trigger a maintenance action
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
function handleMaintenanceAction(
|
|
203
|
+
req: IncomingMessage,
|
|
204
|
+
res: ServerResponse,
|
|
205
|
+
ctx: RouteContext,
|
|
206
|
+
): void {
|
|
207
|
+
let body = "";
|
|
208
|
+
req.setEncoding("utf8");
|
|
209
|
+
req.on("data", (chunk: string) => {
|
|
210
|
+
body += chunk;
|
|
211
|
+
if (body.length > 10_000) req.destroy();
|
|
212
|
+
});
|
|
213
|
+
req.on("end", () => {
|
|
214
|
+
let payload: MaintenanceActionBody;
|
|
215
|
+
try {
|
|
216
|
+
payload = JSON.parse(body) as MaintenanceActionBody;
|
|
217
|
+
if (!payload || !payload.action) {
|
|
218
|
+
sendJson(res, 400, { error: "missing 'action' field" });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
sendJson(res, 400, { error: "invalid JSON" });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const m = lazyMaintenance();
|
|
228
|
+
const u = lazyStoreUtils();
|
|
229
|
+
// All destructive actions: temp backup first
|
|
230
|
+
if (payload.action !== "integrity-check") {
|
|
231
|
+
// Non-blocking — backup failure is non-fatal
|
|
232
|
+
m.backupDb(ctx.stateDir);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
switch (payload.action) {
|
|
236
|
+
case "vacuum": {
|
|
237
|
+
const r = m.vacuumDb(ctx.stateDir);
|
|
238
|
+
sendJson(res, 200, {
|
|
239
|
+
operation: "vacuum",
|
|
240
|
+
success: true,
|
|
241
|
+
affected: r.affected,
|
|
242
|
+
reclaimedBytes: r.reclaimedBytes,
|
|
243
|
+
summary: r.summary,
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
case "checkpoint": {
|
|
248
|
+
const r = m.checkpointWal(ctx.stateDir);
|
|
249
|
+
sendJson(res, 200, {
|
|
250
|
+
operation: "checkpoint",
|
|
251
|
+
success: true,
|
|
252
|
+
affected: r.affected,
|
|
253
|
+
reclaimedBytes: r.reclaimedBytes,
|
|
254
|
+
summary: r.summary,
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
case "reindex": {
|
|
259
|
+
const db = u.openStore(ctx.stateDir);
|
|
260
|
+
db.exec("REINDEX");
|
|
261
|
+
sendJson(res, 200, {
|
|
262
|
+
operation: "reindex",
|
|
263
|
+
success: true,
|
|
264
|
+
affected: 0,
|
|
265
|
+
reclaimedBytes: 0,
|
|
266
|
+
summary: "REINDEX completed",
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
case "fts5-rebuild": {
|
|
271
|
+
const db = u.openStore(ctx.stateDir);
|
|
272
|
+
db.exec(
|
|
273
|
+
"INSERT INTO context_chunks_trgm(context_chunks_trgm) VALUES('rebuild')",
|
|
274
|
+
);
|
|
275
|
+
sendJson(res, 200, {
|
|
276
|
+
operation: "fts5-rebuild",
|
|
277
|
+
success: true,
|
|
278
|
+
affected: 0,
|
|
279
|
+
reclaimedBytes: 0,
|
|
280
|
+
summary: "FTS5 rebuild triggered",
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
case "reconcile-dedup": {
|
|
285
|
+
const r = m.reconcileDedupMirror(ctx.stateDir);
|
|
286
|
+
const lines: string[] = [];
|
|
287
|
+
if (r.fixedRefCount) lines.push(`${r.fixedRefCount} ref_counts corrected`);
|
|
288
|
+
if (r.orphansDeleted) lines.push(`${r.orphansDeleted} orphans deleted`);
|
|
289
|
+
if (r.refsBackfilled) lines.push(`${r.refsBackfilled} refs backfilled`);
|
|
290
|
+
sendJson(res, 200, {
|
|
291
|
+
operation: "reconcile-dedup",
|
|
292
|
+
success: true,
|
|
293
|
+
affected: r.fixedRefCount + r.orphansDeleted + r.refsBackfilled,
|
|
294
|
+
reclaimedBytes: 0,
|
|
295
|
+
summary: lines.length ? lines.join(", ") : "nothing to fix",
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
case "prune": {
|
|
300
|
+
const days = Number(payload.daysOld) || 30;
|
|
301
|
+
const r = m.pruneOldRows(ctx.stateDir, days);
|
|
302
|
+
sendJson(res, 200, {
|
|
303
|
+
operation: "prune",
|
|
304
|
+
success: true,
|
|
305
|
+
affected: r.affected,
|
|
306
|
+
reclaimedBytes: r.reclaimedBytes,
|
|
307
|
+
summary: r.summary,
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
case "integrity-check": {
|
|
312
|
+
const lines = m.integrityCheck(ctx.stateDir);
|
|
313
|
+
sendJson(res, 200, {
|
|
314
|
+
operation: "integrity-check",
|
|
315
|
+
success: lines.length === 1 && lines[0] === "ok",
|
|
316
|
+
affected: 0,
|
|
317
|
+
reclaimedBytes: 0,
|
|
318
|
+
summary: lines.join("; "),
|
|
319
|
+
detail: lines,
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
default:
|
|
324
|
+
sendJson(res, 400, {
|
|
325
|
+
error: `unknown action: ${payload.action}`,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
sendJson(res, 500, { error: (err as Error).message });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// JSON helper
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
function sendJson(res: ServerResponse, status: number, data: unknown): void {
|
|
339
|
+
const body = JSON.stringify(data);
|
|
340
|
+
res.writeHead(status, {
|
|
341
|
+
"Content-Type": "application/json",
|
|
342
|
+
"Content-Length": Buffer.byteLength(body),
|
|
343
|
+
});
|
|
344
|
+
res.end(body);
|
|
345
|
+
}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
handleSessions,
|
|
37
37
|
handleTopics,
|
|
38
38
|
handleTurns,
|
|
39
|
+
handleMaintenance,
|
|
39
40
|
handleStatic,
|
|
40
41
|
} from "./routes.js";
|
|
41
42
|
|
|
@@ -223,6 +224,7 @@ export async function launchDashboardServer(
|
|
|
223
224
|
if (handleSessions(req, res, ctx)) return;
|
|
224
225
|
if (handleTopics(req, res, ctx)) return;
|
|
225
226
|
if (handleTurns(req, res, ctx)) return;
|
|
227
|
+
if (handleMaintenance(req, res, ctx)) return;
|
|
226
228
|
handleStatic(req, res, ctx);
|
|
227
229
|
});
|
|
228
230
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* All pi-agnostic, all parameterized (PREVENT-002), all local (PREVENT-PI-004).
|
|
5
5
|
* Exposed via the /mega-db-* slash commands in extensions/mega-db-cmds.ts.
|
|
6
6
|
*/
|
|
7
|
-
import { statSync } from "node:fs";
|
|
7
|
+
import { statSync, mkdirSync, existsSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
9
10
|
import { getStateDir } from "../../store.js";
|
|
10
11
|
import { openStore, withTx } from "./utils.js";
|
|
11
12
|
|
|
@@ -100,6 +101,24 @@ export interface MaintenanceResult {
|
|
|
100
101
|
summary: string;
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Lightweight temp backup of the main sqlite.db via VACUUM INTO.
|
|
106
|
+
*
|
|
107
|
+
* Creates a one-off backup in the OS tmpdir. Returns the backup path.
|
|
108
|
+
* VACUUM INTO produces a clean, defragmented copy — also serves as a
|
|
109
|
+
* pre-op safeguard for any destructive maintenance action.
|
|
110
|
+
*/
|
|
111
|
+
export function backupDb(stateDir: string = getStateDir()): string {
|
|
112
|
+
const backupDir = join(tmpdir(), "pi-mega-compact-backups");
|
|
113
|
+
if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true });
|
|
114
|
+
const ts = Date.now();
|
|
115
|
+
const backupPath = join(backupDir, `sqlite-${ts}.db`);
|
|
116
|
+
const db = openStore(stateDir);
|
|
117
|
+
// guardrails-disable-next-line PREVENT-002: path built from tmpdir + timestamp, not user input
|
|
118
|
+
db.exec(`VACUUM INTO '${backupPath.replace(/'/g, "''")}'`);
|
|
119
|
+
return backupPath;
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
/**
|
|
104
123
|
* Prune raw_transcript + checkpoint_epochs rows older than `daysOld`.
|
|
105
124
|
* Uses `message_timestamp` (raw_transcript) and `created_at` (epochs), both
|