nexo-brain 7.20.12 → 7.20.14
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/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/auto_update.py +49 -13
- package/src/db_guard.py +434 -5
- package/src/doctor/providers/boot.py +194 -0
- package/src/local_context/__init__.py +4 -0
- package/src/local_context/api.py +123 -11
- package/src/plugins/recover.py +40 -18
- package/src/plugins/update.py +72 -13
- package/src/scripts/backfill_task_owner.py +34 -0
- package/src/scripts/nexo-local-index.py +45 -12
- package/src/scripts/prune_runtime_backups.py +3 -3
- package/src/server.py +13 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.14",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `7.20.
|
|
21
|
+
Version `7.20.14` is the current packaged-runtime line. Patch release over v7.20.13 — Brain protects Local Memory during update/recovery paths, rotates runtime backup families to the latest 5 entries, keeps first-indexing status stable, and exposes bounded indexing speed profiles for Desktop.
|
|
22
|
+
|
|
23
|
+
Previously in `7.20.13`: patch release over v7.20.12 — Brain recovery now pauses all known DB writers before restoring `nexo.db`, and Doctor can repair the zero-byte/locked database state that made Desktop Local Memory show zero files.
|
|
24
|
+
|
|
25
|
+
Previously in `7.20.12`: patch release over v7.20.11 — Local Context now keeps the first index pass separate from live change tracking, persists the current indexing start time, caps compact context payloads for agents, and installs the Windows host scheduler needed to keep WSL indexing alive after reboots.
|
|
22
26
|
|
|
23
27
|
Previously in `7.20.11`: patch release over v7.20.10 — Local Context now starts from real system volume roots plus mounted/removable/network volumes, filters system/cache/app/product artifacts, and injects relevant local evidence automatically into heartbeat, task-open and pre-action context.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.14",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/auto_update.py
CHANGED
|
@@ -1336,7 +1336,7 @@ def _reload_launch_agents_after_bump() -> dict:
|
|
|
1336
1336
|
return result
|
|
1337
1337
|
|
|
1338
1338
|
|
|
1339
|
-
AUTO_UPDATE_BACKUP_KEEP =
|
|
1339
|
+
AUTO_UPDATE_BACKUP_KEEP = 5
|
|
1340
1340
|
"""Maximum number of auto-update backups to keep per prefix.
|
|
1341
1341
|
|
|
1342
1342
|
Both `pre-autoupdate-*/` (DB snapshots) and `runtime-tree-*/` (code mirrors)
|
|
@@ -1409,10 +1409,13 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1409
1409
|
CRITICAL_TABLES,
|
|
1410
1410
|
HOURLY_BACKUP_MAX_AGE,
|
|
1411
1411
|
MIN_REFERENCE_ROWS,
|
|
1412
|
+
PROTECTED_TABLES,
|
|
1412
1413
|
db_looks_wiped,
|
|
1413
1414
|
db_row_counts,
|
|
1415
|
+
find_best_hourly_backup,
|
|
1414
1416
|
find_latest_hourly_backup,
|
|
1415
|
-
|
|
1417
|
+
quiesce_nexo_db_writers,
|
|
1418
|
+
resume_nexo_launchagents,
|
|
1416
1419
|
safe_sqlite_backup,
|
|
1417
1420
|
validate_backup_matches_source,
|
|
1418
1421
|
)
|
|
@@ -1423,11 +1426,16 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1423
1426
|
primary = DATA_DIR / "nexo.db"
|
|
1424
1427
|
if not primary.is_file():
|
|
1425
1428
|
return None
|
|
1426
|
-
if not db_looks_wiped(primary,
|
|
1429
|
+
if not db_looks_wiped(primary, PROTECTED_TABLES):
|
|
1427
1430
|
return None
|
|
1428
|
-
reference =
|
|
1431
|
+
reference = find_best_hourly_backup(
|
|
1429
1432
|
paths.backups_dir(),
|
|
1430
1433
|
max_age_seconds=HOURLY_BACKUP_MAX_AGE,
|
|
1434
|
+
tables=PROTECTED_TABLES,
|
|
1435
|
+
) or find_latest_hourly_backup(
|
|
1436
|
+
paths.backups_dir(),
|
|
1437
|
+
max_age_seconds=HOURLY_BACKUP_MAX_AGE,
|
|
1438
|
+
tables=PROTECTED_TABLES,
|
|
1431
1439
|
)
|
|
1432
1440
|
if reference is None:
|
|
1433
1441
|
_log("self-heal: nexo.db looks wiped but no usable hourly backup found — skipping.")
|
|
@@ -1436,7 +1444,7 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1436
1444
|
"reason": "no_usable_hourly_backup",
|
|
1437
1445
|
"primary_db": str(primary),
|
|
1438
1446
|
}
|
|
1439
|
-
ref_counts = db_row_counts(reference,
|
|
1447
|
+
ref_counts = db_row_counts(reference, PROTECTED_TABLES)
|
|
1440
1448
|
ref_total = sum(v for v in ref_counts.values() if isinstance(v, int))
|
|
1441
1449
|
if ref_total < MIN_REFERENCE_ROWS:
|
|
1442
1450
|
_log(f"self-heal: reference backup {reference.name} has {ref_total} rows, below floor {MIN_REFERENCE_ROWS}")
|
|
@@ -1467,11 +1475,29 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1467
1475
|
f"(reference={reference.name}, {ref_total} critical rows). Restoring..."
|
|
1468
1476
|
)
|
|
1469
1477
|
|
|
1470
|
-
#
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1478
|
+
# Pause any live DB writers so they cannot overwrite the restored DB or
|
|
1479
|
+
# keep stale handles open. Desktop installs have more writers than the MCP
|
|
1480
|
+
# server: local-index, email-monitor, followup-runner, watchdog and catchup.
|
|
1481
|
+
quiesce_report = quiesce_nexo_db_writers(dry_run=False)
|
|
1482
|
+
stopped_launchagents = list((quiesce_report.get("launchagents") or {}).get("stopped") or [])
|
|
1483
|
+
if quiesce_report.get("terminated") or stopped_launchagents:
|
|
1484
|
+
_log(
|
|
1485
|
+
"self-heal: quiesced DB writers "
|
|
1486
|
+
f"(terminated={quiesce_report.get('terminated', 0)}, "
|
|
1487
|
+
f"launchagents={len(stopped_launchagents)})."
|
|
1488
|
+
)
|
|
1489
|
+
if quiesce_report.get("errors"):
|
|
1490
|
+
_log(f"self-heal: DB writer quiesce warnings: {quiesce_report.get('errors')}")
|
|
1491
|
+
|
|
1492
|
+
def _resume_quiesced() -> dict | None:
|
|
1493
|
+
if not stopped_launchagents:
|
|
1494
|
+
return None
|
|
1495
|
+
report = resume_nexo_launchagents(stopped_launchagents)
|
|
1496
|
+
if report.get("started"):
|
|
1497
|
+
_log(f"self-heal: resumed {len(report['started'])} launchagent(s).")
|
|
1498
|
+
if report.get("errors"):
|
|
1499
|
+
_log(f"self-heal: launchagent resume warnings: {report.get('errors')}")
|
|
1500
|
+
return report
|
|
1475
1501
|
|
|
1476
1502
|
# Snapshot the current (wiped) state so the heal is reversible.
|
|
1477
1503
|
pre_heal_dir = paths.backups_dir() / f"pre-heal-{time.strftime('%Y-%m-%d-%H%M%S')}"
|
|
@@ -1497,27 +1523,34 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1497
1523
|
ok, err = safe_sqlite_backup(reference, primary)
|
|
1498
1524
|
if not ok:
|
|
1499
1525
|
_log(f"self-heal: restore copy failed: {err}")
|
|
1526
|
+
resume_report = _resume_quiesced()
|
|
1500
1527
|
return {
|
|
1501
1528
|
"action": "failed",
|
|
1502
1529
|
"reason": "restore_copy_failed",
|
|
1503
1530
|
"error": err,
|
|
1504
1531
|
"reference": str(reference),
|
|
1505
1532
|
"pre_heal_dir": str(pre_heal_dir),
|
|
1533
|
+
"quiesce": quiesce_report,
|
|
1534
|
+
"resume": resume_report,
|
|
1506
1535
|
}
|
|
1507
|
-
valid, valid_err = validate_backup_matches_source(reference, primary,
|
|
1536
|
+
valid, valid_err = validate_backup_matches_source(reference, primary, PROTECTED_TABLES)
|
|
1508
1537
|
if not valid:
|
|
1509
1538
|
_log(f"self-heal: post-restore validation failed: {valid_err}")
|
|
1539
|
+
resume_report = _resume_quiesced()
|
|
1510
1540
|
return {
|
|
1511
1541
|
"action": "failed",
|
|
1512
1542
|
"reason": "validation_failed",
|
|
1513
1543
|
"error": valid_err,
|
|
1514
1544
|
"reference": str(reference),
|
|
1515
1545
|
"pre_heal_dir": str(pre_heal_dir),
|
|
1546
|
+
"quiesce": quiesce_report,
|
|
1547
|
+
"resume": resume_report,
|
|
1516
1548
|
}
|
|
1517
1549
|
|
|
1518
|
-
final_counts = db_row_counts(primary,
|
|
1550
|
+
final_counts = db_row_counts(primary, PROTECTED_TABLES)
|
|
1519
1551
|
final_total = sum(v for v in final_counts.values() if isinstance(v, int))
|
|
1520
1552
|
_log(f"self-heal: restored {final_total} critical rows from {reference.name}.")
|
|
1553
|
+
resume_report = _resume_quiesced()
|
|
1521
1554
|
try:
|
|
1522
1555
|
SELF_HEAL_STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
1523
1556
|
SELF_HEAL_STATE_FILE.write_text(json.dumps({
|
|
@@ -1525,6 +1558,7 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1525
1558
|
"reference": str(reference),
|
|
1526
1559
|
"critical_rows_restored": final_total,
|
|
1527
1560
|
"pre_heal_dir": str(pre_heal_dir),
|
|
1561
|
+
"quiesced_launchagents": stopped_launchagents,
|
|
1528
1562
|
}))
|
|
1529
1563
|
except Exception as e:
|
|
1530
1564
|
_log(f"self-heal: state write warning: {e}")
|
|
@@ -1535,7 +1569,9 @@ def _self_heal_if_wiped() -> dict | None:
|
|
|
1535
1569
|
"reference_rows": ref_total,
|
|
1536
1570
|
"restored_rows": final_total,
|
|
1537
1571
|
"pre_heal_dir": str(pre_heal_dir),
|
|
1538
|
-
"terminated_servers":
|
|
1572
|
+
"terminated_servers": int((quiesce_report.get("mcp") or {}).get("terminated") or 0),
|
|
1573
|
+
"quiesce": quiesce_report,
|
|
1574
|
+
"resume": resume_report,
|
|
1539
1575
|
}
|
|
1540
1576
|
|
|
1541
1577
|
|