fix: DB backup on schema change, MCP child cleanup, codex models, usage providers OOM
- Backup DB only on real SCHEMA_VERSION change, not every app version bump - Kill idle MCP stdio bridge children to prevent orphan process leaks - Add getDistinctProviders to avoid loading every row JSON blob (OOM fix) - Update codex model list (gpt-5.6 sol/terra/luna, drop 5.3 codex variants) - Reorder Claude default models (fable first) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
0270f6ea70
commit
b25e10160d
13 files changed
+371
-45
No files matched your search
+41
-1
@@ -1,9 +1,20 @@
|
||||
// DB safety backups — taken ONLY before a schema change (see migrate.js).
|
||||
//
|
||||
// ⚠️ AGENT/DEV NOTES:
|
||||
// - Backups are a best-effort safety net before schema migrations. There is NO
|
||||
// automated restore path; recovery is manual (copy a backup file back).
|
||||
// - Backups intentionally EXCLUDE the `requestDetails` table (observability log,
|
||||
// auto-pruned, non-critical) so a multi-hundred-MB DB backs up as a few MB.
|
||||
// - Only the newest KEEP_BACKUPS are kept; older ones are pruned automatically.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { BACKUPS_DIR, ensureDirs } from "./paths.js";
|
||||
import { timestampSlug, getAppVersion } from "./version.js";
|
||||
|
||||
const KEEP_BACKUPS = 5;
|
||||
const KEEP_BACKUPS = 3;
|
||||
|
||||
// Tables excluded from safety backups (large, non-critical, reproducible).
|
||||
const BACKUP_EXCLUDE_TABLES = ["requestDetails"];
|
||||
|
||||
export function makeBackupDir(label) {
|
||||
ensureDirs();
|
||||
@@ -22,6 +33,35 @@ export function backupFile(srcPath, destDir, destName = null) {
|
||||
return dest;
|
||||
}
|
||||
|
||||
// Lightweight DB backup via ATTACH: create an empty sqlite file, copy every
|
||||
// table EXCEPT the excluded ones into it. Avoids duplicating the huge
|
||||
// observability log, so the backup stays small regardless of DB size.
|
||||
export function backupDbLite(adapter, destDir, destName = "data.sqlite") {
|
||||
const dest = path.join(destDir, destName);
|
||||
try { fs.rmSync(dest, { force: true }); } catch {}
|
||||
const escaped = dest.replace(/'/g, "''");
|
||||
|
||||
adapter.exec(`ATTACH DATABASE '${escaped}' AS bak`);
|
||||
try {
|
||||
const excluded = new Set(BACKUP_EXCLUDE_TABLES);
|
||||
const tables = adapter
|
||||
.all(`SELECT name, sql FROM main.sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`)
|
||||
.filter((t) => !excluded.has(t.name));
|
||||
|
||||
adapter.transaction(() => {
|
||||
for (const t of tables) {
|
||||
// Recreate table structure in backup DB, then copy rows.
|
||||
const createSql = t.sql.replace(/CREATE TABLE\s+/i, "CREATE TABLE bak.");
|
||||
adapter.exec(createSql);
|
||||
adapter.exec(`INSERT INTO bak.${t.name} SELECT * FROM main.${t.name}`);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
try { adapter.exec("DETACH DATABASE bak"); } catch {}
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
export function pruneOldBackups() {
|
||||
if (!fs.existsSync(BACKUPS_DIR)) return;
|
||||
const entries = fs.readdirSync(BACKUPS_DIR, { withFileTypes: true })
|
||||
|
||||
Reference in new issue
Block a user