nexo-brain 7.20.14 → 7.20.16
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/README.md +5 -1
- package/package.json +1 -1
- package/src/auto_update.py +22 -1
- package/src/plugins/update.py +44 -6
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.16` is the current packaged-runtime line. Patch release over v7.20.15 — packaged updates now keep the `local_context` runtime shim importable and rollback code-tree snapshots safely when compatibility directories are symlinks.
|
|
22
|
+
|
|
23
|
+
Previously in `7.20.15`: patch release over v7.20.14 — Brain update/recovery paths now fail closed when the DB guard is missing or stale, and backup validation rejects any replacement that loses Local Memory tables.
|
|
24
|
+
|
|
25
|
+
Previously in `7.20.14`: 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
26
|
|
|
23
27
|
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
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.16",
|
|
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
|
@@ -82,6 +82,23 @@ TEMPLATE_FILE = _RESOLVED_REPO_DIR / "templates" / "CLAUDE.md.template"
|
|
|
82
82
|
CHECK_COOLDOWN_SECONDS = 3600 # 1 hour
|
|
83
83
|
GIT_TIMEOUT_SECONDS = 4 # stay well under the 5s total budget
|
|
84
84
|
CRITICAL_BACKUP_TABLES = ("learnings", "session_diary", "guard_checks", "protocol_debt")
|
|
85
|
+
LOCAL_CONTEXT_BACKUP_TABLES = (
|
|
86
|
+
"local_index_roots",
|
|
87
|
+
"local_index_exclusions",
|
|
88
|
+
"local_index_jobs",
|
|
89
|
+
"local_index_checkpoints",
|
|
90
|
+
"local_index_state",
|
|
91
|
+
"local_index_errors",
|
|
92
|
+
"local_assets",
|
|
93
|
+
"local_asset_versions",
|
|
94
|
+
"local_chunks",
|
|
95
|
+
"local_entities",
|
|
96
|
+
"local_relations",
|
|
97
|
+
"local_embeddings",
|
|
98
|
+
"local_context_queries",
|
|
99
|
+
"local_index_dirs",
|
|
100
|
+
)
|
|
101
|
+
PROTECTED_BACKUP_TABLES = CRITICAL_BACKUP_TABLES + LOCAL_CONTEXT_BACKUP_TABLES
|
|
85
102
|
CLASSIFIER_INSTALL_TIMEOUT_SECONDS = 1800
|
|
86
103
|
CLASSIFIER_INSTALL_JOIN_SECONDS = 1500
|
|
87
104
|
CLASSIFIER_INSTALL_LOG = paths.logs_dir() / "classifier-install.log"
|
|
@@ -321,7 +338,7 @@ def _validate_db_backup(source_db: Path, backup_db: Path) -> dict:
|
|
|
321
338
|
report["errors"].append(f"backup db missing: {backup_db}")
|
|
322
339
|
return report
|
|
323
340
|
|
|
324
|
-
for table in
|
|
341
|
+
for table in PROTECTED_BACKUP_TABLES:
|
|
325
342
|
source_count = _critical_table_count(source_db, table)
|
|
326
343
|
backup_count = _critical_table_count(backup_db, table)
|
|
327
344
|
report["source_counts"][table] = source_count
|
|
@@ -2423,6 +2440,7 @@ def _f06_legacy_shim_map() -> list[tuple[str, Path]]:
|
|
|
2423
2440
|
("plugins", core_root / "plugins"),
|
|
2424
2441
|
("hooks", core_root / "hooks"),
|
|
2425
2442
|
("rules", core_root / "rules"),
|
|
2443
|
+
("local_context", core_root / "local_context"),
|
|
2426
2444
|
("data", NEXO_HOME / "runtime" / "data"),
|
|
2427
2445
|
("logs", NEXO_HOME / "runtime" / "logs"),
|
|
2428
2446
|
("operations", NEXO_HOME / "runtime" / "operations"),
|
|
@@ -2491,6 +2509,7 @@ def _f06_live_legacy_paths() -> list[Path]:
|
|
|
2491
2509
|
"plugins",
|
|
2492
2510
|
"hooks",
|
|
2493
2511
|
"rules",
|
|
2512
|
+
"local_context",
|
|
2494
2513
|
"db",
|
|
2495
2514
|
"dashboard",
|
|
2496
2515
|
"skills-core",
|
|
@@ -2576,6 +2595,7 @@ def _promote_packaged_runtime_code_to_core() -> None:
|
|
|
2576
2595
|
("cognitive", core_root / "cognitive"),
|
|
2577
2596
|
("doctor", core_root / "doctor"),
|
|
2578
2597
|
("dashboard", core_root / "dashboard"),
|
|
2598
|
+
("local_context", core_root / "local_context"),
|
|
2579
2599
|
("skills-core", core_root / "skills"),
|
|
2580
2600
|
]
|
|
2581
2601
|
|
|
@@ -4308,6 +4328,7 @@ def _backup_runtime_tree(dest: Path = NEXO_HOME) -> str:
|
|
|
4308
4328
|
"db",
|
|
4309
4329
|
"cognitive",
|
|
4310
4330
|
"dashboard",
|
|
4331
|
+
"local_context",
|
|
4311
4332
|
"rules",
|
|
4312
4333
|
"crons",
|
|
4313
4334
|
"scripts",
|
package/src/plugins/update.py
CHANGED
|
@@ -32,10 +32,9 @@ except ModuleNotFoundError as exc:
|
|
|
32
32
|
def is_duplicate_artifact_name(_path) -> bool:
|
|
33
33
|
return False
|
|
34
34
|
|
|
35
|
-
# db_guard
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# the fixed module on its own.
|
|
35
|
+
# db_guard must be available before any DB-touching update runs. Desktop syncs
|
|
36
|
+
# this file plus db_guard.py as a pair; if a stale runtime imports update.py
|
|
37
|
+
# without a current guard, we abort instead of risking a local-memory wipe.
|
|
39
38
|
try:
|
|
40
39
|
from db_guard import (
|
|
41
40
|
CRITICAL_TABLES,
|
|
@@ -52,8 +51,10 @@ try:
|
|
|
52
51
|
validate_backup_matches_source,
|
|
53
52
|
)
|
|
54
53
|
_DB_GUARD_AVAILABLE = True
|
|
55
|
-
|
|
54
|
+
_DB_GUARD_IMPORT_ERROR = ""
|
|
55
|
+
except Exception as exc: # pragma: no cover - exercised only during stale installs
|
|
56
56
|
_DB_GUARD_AVAILABLE = False
|
|
57
|
+
_DB_GUARD_IMPORT_ERROR = str(exc)
|
|
57
58
|
CRITICAL_TABLES = ()
|
|
58
59
|
PROTECTED_TABLES = ()
|
|
59
60
|
HOURLY_BACKUP_MAX_AGE = 48 * 3600
|
|
@@ -90,6 +91,13 @@ except Exception: # pragma: no cover - exercised only during mid-upgrade instal
|
|
|
90
91
|
def validate_backup_matches_source(*_args, **_kwargs): # type: ignore[misc]
|
|
91
92
|
return True, None
|
|
92
93
|
|
|
94
|
+
REQUIRED_LOCAL_MEMORY_TABLES: tuple[str, ...] = (
|
|
95
|
+
"local_assets",
|
|
96
|
+
"local_chunks",
|
|
97
|
+
"local_embeddings",
|
|
98
|
+
"local_index_dirs",
|
|
99
|
+
)
|
|
100
|
+
|
|
93
101
|
# Code root is the parent of plugins/:
|
|
94
102
|
# - source checkout: <repo>/src
|
|
95
103
|
# - packaged runtime: <NEXO_HOME>
|
|
@@ -383,6 +391,30 @@ def _check_dirty() -> str | None:
|
|
|
383
391
|
return None
|
|
384
392
|
|
|
385
393
|
|
|
394
|
+
def _db_guard_integrity_error() -> str | None:
|
|
395
|
+
"""Return why DB protection is unsafe, or None when current guard is usable."""
|
|
396
|
+
if os.environ.get("NEXO_SKIP_WIPE_GUARD") == "1":
|
|
397
|
+
return None
|
|
398
|
+
primary_db = DATA_DIR / "nexo.db"
|
|
399
|
+
if not primary_db.is_file():
|
|
400
|
+
return None
|
|
401
|
+
if not _DB_GUARD_AVAILABLE:
|
|
402
|
+
return (
|
|
403
|
+
"DB protection module is not available; refusing to update while local memory may exist.\n"
|
|
404
|
+
f" import error: {_DB_GUARD_IMPORT_ERROR or 'unknown'}\n"
|
|
405
|
+
"Restart Desktop so it can sync the bundled Brain guard, then retry the update."
|
|
406
|
+
)
|
|
407
|
+
protected = set(PROTECTED_TABLES)
|
|
408
|
+
missing = [table for table in REQUIRED_LOCAL_MEMORY_TABLES if table not in protected]
|
|
409
|
+
if missing:
|
|
410
|
+
return (
|
|
411
|
+
"DB protection module is stale; refusing to update because local memory tables are not protected.\n"
|
|
412
|
+
f" missing tables: {', '.join(missing)}\n"
|
|
413
|
+
"Restart Desktop so it can sync the bundled Brain guard, then retry the update."
|
|
414
|
+
)
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
|
|
386
418
|
def _preflight_wipe_check() -> str | None:
|
|
387
419
|
"""Abort the update early if the primary DB already looks wiped.
|
|
388
420
|
|
|
@@ -399,6 +431,9 @@ def _preflight_wipe_check() -> str | None:
|
|
|
399
431
|
"""
|
|
400
432
|
if os.environ.get("NEXO_SKIP_WIPE_GUARD") == "1":
|
|
401
433
|
return None
|
|
434
|
+
guard_err = _db_guard_integrity_error()
|
|
435
|
+
if guard_err:
|
|
436
|
+
return guard_err
|
|
402
437
|
primary_db = DATA_DIR / "nexo.db"
|
|
403
438
|
if not primary_db.is_file():
|
|
404
439
|
return None # Nothing to protect; fresh install path.
|
|
@@ -873,6 +908,7 @@ def _backup_code_tree() -> tuple[str | None, str | None]:
|
|
|
873
908
|
"db",
|
|
874
909
|
"cognitive",
|
|
875
910
|
"dashboard",
|
|
911
|
+
"local_context",
|
|
876
912
|
"rules",
|
|
877
913
|
"crons",
|
|
878
914
|
"scripts",
|
|
@@ -917,7 +953,9 @@ def _restore_code_tree(backup_dir: str) -> str | None:
|
|
|
917
953
|
for item in bdir.iterdir():
|
|
918
954
|
dest = NEXO_HOME / item.name
|
|
919
955
|
if item.is_dir():
|
|
920
|
-
if dest.
|
|
956
|
+
if dest.is_symlink():
|
|
957
|
+
dest.unlink()
|
|
958
|
+
elif dest.is_dir():
|
|
921
959
|
shutil.rmtree(dest)
|
|
922
960
|
shutil.copytree(item, dest)
|
|
923
961
|
elif item.is_file():
|