nexo-brain 7.30.27 → 7.30.28

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.30.27",
3
+ "version": "7.30.28",
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,9 @@
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.30.27` is the current packaged-runtime line. Patch release over v7.30.26 - post-update repair now recovers core scripts archived by older F0.6 shim reconciliation and refreshes `core/current` from `core`, so same-version snapshots cannot keep stale watchdog code.
21
+ Version `7.30.28` is the current packaged-runtime line. Patch release over v7.30.27 - F0.6 runtime repairs now run through an existing post-install hook, so older updaters execute script-conflict recovery and `core/current` refresh on the first upgrade.
22
+
23
+ Previously in `7.30.27`: patch release over v7.30.26 - post-update repair now recovers core scripts archived by older F0.6 shim reconciliation and refreshes `core/current` from `core`, so same-version snapshots cannot keep stale watchdog code.
22
24
 
23
25
  Previously in `7.30.26`: patch release over v7.30.25 - `nexo update` now copies packaged core scripts through the F0.6 `scripts -> core/scripts` shim, so watchdog fixes reach the active LaunchAgent path.
24
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "7.30.27",
3
+ "version": "7.30.28",
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",
@@ -5444,10 +5444,20 @@ def _persist_guardian_hard_defaults(dest: Path) -> tuple[bool, str | None]:
5444
5444
  non-default value (``shadow`` / ``off`` / anything else). Only
5445
5445
  fills missing keys or upgrades explicit defaults.
5446
5446
  """
5447
+ def _join_messages(*messages: str | None) -> str | None:
5448
+ parts = [str(message) for message in messages if str(message or "").strip()]
5449
+ return ";".join(parts) if parts else None
5450
+
5451
+ repair_message = None
5452
+ try:
5453
+ _repair_changed, repair_message = _run_f06_post_update_repairs(dest)
5454
+ except Exception as exc:
5455
+ repair_message = f"f06-post-update-repair-warning:{exc.__class__.__name__}"
5456
+
5447
5457
  if _is_ephemeral_runtime_install(dest):
5448
- return False, "guardian-hard-persist-skipped:ephemeral"
5458
+ return False, _join_messages("guardian-hard-persist-skipped:ephemeral", repair_message)
5449
5459
  if os.environ.get("NEXO_GUARDIAN_PERSIST_HARD", "").strip().lower() == "off":
5450
- return False, "guardian-hard-persist-skipped:operator-opt-out"
5460
+ return False, _join_messages("guardian-hard-persist-skipped:operator-opt-out", repair_message)
5451
5461
 
5452
5462
  config_path = dest / "personal" / "config" / "guardian-runtime-overrides.json"
5453
5463
  config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -5477,16 +5487,16 @@ def _persist_guardian_hard_defaults(dest: Path) -> tuple[bool, str | None]:
5477
5487
  current[key] = default
5478
5488
 
5479
5489
  if current == original and config_path.is_file():
5480
- return False, None
5490
+ return False, repair_message
5481
5491
 
5482
5492
  try:
5483
5493
  tmp_path = config_path.with_suffix(config_path.suffix + ".tmp")
5484
5494
  tmp_path.write_text(json.dumps(current, indent=2, ensure_ascii=False) + "\n")
5485
5495
  tmp_path.replace(config_path)
5486
5496
  except Exception as exc: # pragma: no cover - filesystem failures
5487
- return False, f"guardian-hard-persist-error:{exc.__class__.__name__}"
5497
+ return False, _join_messages(f"guardian-hard-persist-error:{exc.__class__.__name__}", repair_message)
5488
5498
 
5489
- return True, None
5499
+ return True, repair_message
5490
5500
 
5491
5501
 
5492
5502
  from datetime import datetime as _adaptive_datetime # isolated alias; other modules use ``time.time()``
@@ -5702,12 +5712,27 @@ def _refresh_active_versioned_runtime_snapshot(dest: Path) -> tuple[bool, str |
5702
5712
  return True, f"runtime-snapshot-refreshed:{version}"
5703
5713
 
5704
5714
 
5715
+ def _run_f06_post_update_repairs(dest: Path) -> tuple[bool, str | None]:
5716
+ changed = False
5717
+ messages: list[str] = []
5718
+ for repair in (
5719
+ _promote_f06_core_scripts_from_latest_legacy_conflict,
5720
+ _refresh_active_versioned_runtime_snapshot,
5721
+ ):
5722
+ repair_changed, repair_message = repair(dest)
5723
+ changed = changed or repair_changed
5724
+ if repair_message:
5725
+ messages.append(repair_message)
5726
+ return changed, ";".join(messages) if messages else None
5727
+
5728
+
5705
5729
  # Whitelist of post-install hooks to invoke from the fresh tree. Each entry
5706
5730
  # is the function name inside ``auto_update.py`` of the freshly-copied
5707
5731
  # code. The subprocess resolves them on the NEW module and calls
5708
5732
  # ``fn(dest)`` returning ``(bool, str | None)``. New hooks added in
5709
5733
  # future releases only need an entry here — no extra wiring.
5710
5734
  _POST_INSTALL_FRESH_HOOKS = (
5735
+ ("f06-post-update-repairs", "_run_f06_post_update_repairs"),
5711
5736
  ("f06-core-scripts-promoted", "_promote_f06_core_scripts_from_latest_legacy_conflict"),
5712
5737
  ("runtime-snapshot-refreshed", "_refresh_active_versioned_runtime_snapshot"),
5713
5738
  ("guardian-hard-persisted", "_persist_guardian_hard_defaults"),
@@ -5745,7 +5770,7 @@ def _run_post_install_hooks_fresh(dest: Path, *, env: dict | None = None) -> lis
5745
5770
  "import json, sys\n"
5746
5771
  "from pathlib import Path\n"
5747
5772
  f"sys.path.insert(0, {repr(str(code_root))})\n"
5748
- "hooks = json.loads(" + repr(hook_specs_json) + ")\n"
5773
+ "fallback_hooks = json.loads(" + repr(hook_specs_json) + ")\n"
5749
5774
  f"dest = Path({repr(dest_str)})\n"
5750
5775
  "results = []\n"
5751
5776
  "try:\n"
@@ -5753,6 +5778,11 @@ def _run_post_install_hooks_fresh(dest: Path, *, env: dict | None = None) -> lis
5753
5778
  "except Exception as exc:\n"
5754
5779
  " print(json.dumps({'error': 'import_auto_update_failed', 'detail': repr(exc)}))\n"
5755
5780
  " sys.exit(0)\n"
5781
+ "fresh_hooks = getattr(fresh, '_POST_INSTALL_FRESH_HOOKS', None)\n"
5782
+ "if isinstance(fresh_hooks, (list, tuple)) and fresh_hooks:\n"
5783
+ " hooks = [(str(tag), str(fn)) for tag, fn in fresh_hooks]\n"
5784
+ "else:\n"
5785
+ " hooks = fallback_hooks\n"
5756
5786
  "for tag, fn_name in hooks:\n"
5757
5787
  " fn = getattr(fresh, fn_name, None)\n"
5758
5788
  " if fn is None:\n"