nexo-brain 5.10.0 → 5.10.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/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/auto_update.py +197 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.2",
|
|
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 `5.10.
|
|
21
|
+
Version `5.10.2` is the current packaged-runtime line: auto-bootstraps `brain/profile.json` from `brain/calibration.json` on `nexo update` when the profile file is missing, empty, or corrupt AND calibration carries at least one of `meta.role`, `meta.technical_level`, `name`, `language`. NEXO Desktop's *Preferencias → Avanzado* tab used to render an empty `{}` for that block when the onboarding flow had been interrupted; now it either shows the seeded profile or a friendly explanation of what each file is for, paired with Desktop `v0.11.2` which adds header descriptions to both JSON blocks. Never overwrites a populated profile, never raises, idempotent. Also fixes a latent host-filesystem leak in `test_user_facing_caller_with_no_user_default_uses_alto` exposed by the v5.10.1 migration.
|
|
22
|
+
|
|
23
|
+
Previously in `5.10.1`: silent, one-shot migration that recovers legacy `reasoning_effort="max"` (written by `nexo preferences --reasoning-effort max` before v5.9.0) into the new `preferences.default_resonance` map — any user who had configured `max` before v5.9.0 and never touched the new selector was silently falling back to `DEFAULT_RESONANCE="alto"` on interactive calls since the v5.10.0 update. `_run_runtime_post_sync()` runs `_migrate_effort_to_resonance()` exactly once: `max→maximo`, `xhigh→alto`, `high→medio`, `medium→bajo`. No-op when calibration or schedule already declares an explicit `default_resonance`; idempotent; conservative; never raises.
|
|
24
|
+
|
|
25
|
+
Previously in `5.10.0`: fixes the deep-sleep extract bloat that made Session 1 take ~57 minutes on some installs (new `bare_mode` on `run_automation_prompt` wires `claude --bare` for JSON-only extractor callers — ~4.3× faster per child, sourced from `ANTHROPIC_API_KEY` env or `~/.claude/anthropic-api-key.txt`). `caller=` is now **mandatory** on `run_automation_prompt` — no silent fallback; every automation subprocess traces back to a registered caller with a deliberate tier. Five personal scripts (`personal/email-monitor`, `personal/github-monitor`, `personal/post-x`, `personal/followup-runner`, `personal/orchestrator-v2`) joined the resonance map with tiers picked per caller based on what each one does. gbp/* marketing posts bumped from `medio` to `alto` (public-facing copy, quality first over speed). 65 legacy protocol debts bulk-resolved as part of the audit — the patterns that generated them are structurally closed by mandatory `caller=` + unified session log + bare_mode.
|
|
22
26
|
|
|
23
27
|
Previously in `5.9.1`: adds `default_resonance` to `brain/calibration.json` via the Desktop-facing schema (`nexo schema --json`), so NEXO Desktop's Preferences dialog renders a select with `Máximo` / `Alto (recomendado)` / `Medio` / `Bajo` automatically — no Desktop release needed. `resolve_tier_for_caller` reads calibration first and falls back to the legacy `schedule.json` location. `nexo preferences --resonance` writes both. The UI control only affects interactive sessions (`nexo chat`, Desktop new conversation, interactive `nexo update`); crons and background processes stay pinned per caller in `resonance_map.py`.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.2",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain \u2014 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
|
@@ -875,6 +875,182 @@ def _purge_zero_byte_db_files() -> list[Path]:
|
|
|
875
875
|
return removed
|
|
876
876
|
|
|
877
877
|
|
|
878
|
+
def _migrate_effort_to_resonance(dest: Path = NEXO_HOME) -> list[str]:
|
|
879
|
+
"""Auto-migrate legacy ``reasoning_effort`` preference into the new
|
|
880
|
+
``preferences.default_resonance`` knob.
|
|
881
|
+
|
|
882
|
+
Context: before v5.9.0 the user's power-level preference lived in
|
|
883
|
+
``config/schedule.json`` under
|
|
884
|
+
``client_runtime_profiles.claude_code.reasoning_effort`` — one of
|
|
885
|
+
``max`` / ``xhigh`` / ``high`` / ``medium``. v5.9.0 introduced the
|
|
886
|
+
resonance map, with ``preferences.default_resonance`` (one of
|
|
887
|
+
``maximo`` / ``alto`` / ``medio`` / ``bajo``) written to
|
|
888
|
+
``brain/calibration.json``. When the new code path kicks in, a user
|
|
889
|
+
whose only recorded preference is the legacy effort silently falls
|
|
890
|
+
back to ``DEFAULT_RESONANCE`` (``alto``), losing their prior
|
|
891
|
+
preference. v5.10.0 shipped without a migration for this; v5.10.1
|
|
892
|
+
adds it here.
|
|
893
|
+
|
|
894
|
+
The migration is idempotent and conservative: it only runs when the
|
|
895
|
+
user has NOT set ``default_resonance`` explicitly anywhere (neither
|
|
896
|
+
in ``calibration.json`` nor in ``schedule.json``). That means users
|
|
897
|
+
who already adjusted their preference through the Desktop UI or the
|
|
898
|
+
``nexo preferences --resonance`` CLI keep whatever they chose; this
|
|
899
|
+
migration only recovers the legacy preference for users who never
|
|
900
|
+
touched either.
|
|
901
|
+
|
|
902
|
+
Mapping (mirrors ``_RESONANCE_TABLE`` in ``src/resonance_map.py``):
|
|
903
|
+
max → maximo
|
|
904
|
+
xhigh → alto
|
|
905
|
+
high → medio
|
|
906
|
+
medium → bajo
|
|
907
|
+
|
|
908
|
+
Returns the list of actions taken for logging. Failures are
|
|
909
|
+
swallowed: migration must never block an update.
|
|
910
|
+
"""
|
|
911
|
+
import json as _json
|
|
912
|
+
|
|
913
|
+
actions: list[str] = []
|
|
914
|
+
|
|
915
|
+
cal_path = dest / "brain" / "calibration.json"
|
|
916
|
+
sched_path = dest / "config" / "schedule.json"
|
|
917
|
+
|
|
918
|
+
try:
|
|
919
|
+
cal = _json.loads(cal_path.read_text()) if cal_path.exists() else {}
|
|
920
|
+
if not isinstance(cal, dict):
|
|
921
|
+
cal = {}
|
|
922
|
+
except Exception:
|
|
923
|
+
cal = {}
|
|
924
|
+
|
|
925
|
+
try:
|
|
926
|
+
sched = _json.loads(sched_path.read_text()) if sched_path.exists() else {}
|
|
927
|
+
if not isinstance(sched, dict):
|
|
928
|
+
sched = {}
|
|
929
|
+
except Exception:
|
|
930
|
+
sched = {}
|
|
931
|
+
|
|
932
|
+
existing_cal_pref = ""
|
|
933
|
+
if isinstance(cal.get("preferences"), dict):
|
|
934
|
+
existing_cal_pref = str(cal["preferences"].get("default_resonance") or "").strip().lower()
|
|
935
|
+
existing_sched_pref = str(sched.get("default_resonance") or "").strip().lower()
|
|
936
|
+
|
|
937
|
+
valid_tiers = {"maximo", "alto", "medio", "bajo"}
|
|
938
|
+
if existing_cal_pref in valid_tiers or existing_sched_pref in valid_tiers:
|
|
939
|
+
return actions # user already has an explicit resonance preference
|
|
940
|
+
|
|
941
|
+
# Look up the legacy effort hint.
|
|
942
|
+
effort = ""
|
|
943
|
+
profiles = sched.get("client_runtime_profiles")
|
|
944
|
+
if isinstance(profiles, dict):
|
|
945
|
+
cc = profiles.get("claude_code") if isinstance(profiles.get("claude_code"), dict) else {}
|
|
946
|
+
effort = str(cc.get("reasoning_effort") or "").strip().lower()
|
|
947
|
+
|
|
948
|
+
legacy_map = {
|
|
949
|
+
"max": "maximo",
|
|
950
|
+
"xhigh": "alto",
|
|
951
|
+
"high": "medio",
|
|
952
|
+
"medium": "bajo",
|
|
953
|
+
}
|
|
954
|
+
target_tier = legacy_map.get(effort)
|
|
955
|
+
if not target_tier:
|
|
956
|
+
return actions # nothing usable to migrate from
|
|
957
|
+
|
|
958
|
+
# Write into calibration.json (canonical location for v5.9.1+).
|
|
959
|
+
try:
|
|
960
|
+
cal_path.parent.mkdir(parents=True, exist_ok=True)
|
|
961
|
+
prefs = cal.get("preferences")
|
|
962
|
+
if not isinstance(prefs, dict):
|
|
963
|
+
prefs = {}
|
|
964
|
+
prefs["default_resonance"] = target_tier
|
|
965
|
+
cal["preferences"] = prefs
|
|
966
|
+
cal_path.write_text(_json.dumps(cal, indent=2, ensure_ascii=False) + "\n")
|
|
967
|
+
actions.append(
|
|
968
|
+
f"resonance-migration:{effort}->{target_tier}"
|
|
969
|
+
)
|
|
970
|
+
except Exception as exc:
|
|
971
|
+
actions.append(f"resonance-migration-warning:{exc.__class__.__name__}")
|
|
972
|
+
|
|
973
|
+
return actions
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _bootstrap_profile_from_calibration_meta(dest: Path = NEXO_HOME) -> list[str]:
|
|
977
|
+
"""Create ``brain/profile.json`` from ``calibration.json`` fields when the
|
|
978
|
+
profile file does not exist yet.
|
|
979
|
+
|
|
980
|
+
Context: the onboarding flow documented in CLAUDE.md writes ``role`` and
|
|
981
|
+
``technical_level`` to ``brain/profile.json``. Users who went through the
|
|
982
|
+
2025/early-2026 flow (or whose onboarding was interrupted) ended up with
|
|
983
|
+
those values living under ``calibration.json → meta.role`` and
|
|
984
|
+
``meta.technical_level`` only, with no ``profile.json`` file at all.
|
|
985
|
+
NEXO Desktop's "Preferencias → Avanzado" tab then shows an empty
|
|
986
|
+
``{}`` for ``profile.json`` with no context — confusing for the operator.
|
|
987
|
+
|
|
988
|
+
This migration is conservative and idempotent:
|
|
989
|
+
- Only runs when ``profile.json`` does not exist (or is empty/invalid).
|
|
990
|
+
- Only writes when ``calibration.json`` has at least one of
|
|
991
|
+
``meta.role`` / ``meta.technical_level`` / ``name`` / ``language``.
|
|
992
|
+
- Never overwrites an existing profile.
|
|
993
|
+
- Errors are swallowed into a ``profile-bootstrap-warning:*`` action
|
|
994
|
+
line; the update path never raises.
|
|
995
|
+
"""
|
|
996
|
+
import json as _json
|
|
997
|
+
|
|
998
|
+
actions: list[str] = []
|
|
999
|
+
|
|
1000
|
+
cal_path = dest / "brain" / "calibration.json"
|
|
1001
|
+
profile_path = dest / "brain" / "profile.json"
|
|
1002
|
+
|
|
1003
|
+
if profile_path.exists():
|
|
1004
|
+
try:
|
|
1005
|
+
existing = _json.loads(profile_path.read_text())
|
|
1006
|
+
if isinstance(existing, dict) and existing:
|
|
1007
|
+
return actions # profile already populated, keep untouched
|
|
1008
|
+
except Exception:
|
|
1009
|
+
# Corrupt / empty file — fall through and rewrite below.
|
|
1010
|
+
pass
|
|
1011
|
+
|
|
1012
|
+
if not cal_path.exists():
|
|
1013
|
+
return actions
|
|
1014
|
+
|
|
1015
|
+
try:
|
|
1016
|
+
cal = _json.loads(cal_path.read_text())
|
|
1017
|
+
if not isinstance(cal, dict):
|
|
1018
|
+
return actions
|
|
1019
|
+
except Exception:
|
|
1020
|
+
return actions
|
|
1021
|
+
|
|
1022
|
+
meta = cal.get("meta") if isinstance(cal.get("meta"), dict) else {}
|
|
1023
|
+
payload: dict = {}
|
|
1024
|
+
|
|
1025
|
+
role = str(meta.get("role") or "").strip()
|
|
1026
|
+
tech = str(meta.get("technical_level") or "").strip()
|
|
1027
|
+
name = str(cal.get("name") or "").strip()
|
|
1028
|
+
lang = str(cal.get("language") or "").strip()
|
|
1029
|
+
|
|
1030
|
+
if role:
|
|
1031
|
+
payload["role"] = role
|
|
1032
|
+
if tech:
|
|
1033
|
+
payload["technical_level"] = tech
|
|
1034
|
+
if name:
|
|
1035
|
+
payload["name"] = name
|
|
1036
|
+
if lang:
|
|
1037
|
+
payload["language"] = lang
|
|
1038
|
+
|
|
1039
|
+
if not payload:
|
|
1040
|
+
return actions # nothing to seed the profile with
|
|
1041
|
+
|
|
1042
|
+
payload["source"] = "auto_update._bootstrap_profile_from_calibration_meta"
|
|
1043
|
+
|
|
1044
|
+
try:
|
|
1045
|
+
profile_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1046
|
+
profile_path.write_text(_json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
1047
|
+
actions.append(f"profile-bootstrap:{len(payload)-1}-fields")
|
|
1048
|
+
except Exception as exc:
|
|
1049
|
+
actions.append(f"profile-bootstrap-warning:{exc.__class__.__name__}")
|
|
1050
|
+
|
|
1051
|
+
return actions
|
|
1052
|
+
|
|
1053
|
+
|
|
878
1054
|
def _heal_deep_sleep_runtime(dest: Path = NEXO_HOME) -> list[str]:
|
|
879
1055
|
"""Repair deep-sleep state that older runtimes left in a bad shape.
|
|
880
1056
|
|
|
@@ -2697,6 +2873,27 @@ def _run_runtime_post_sync(dest: Path = NEXO_HOME, progress_fn=None) -> tuple[bo
|
|
|
2697
2873
|
except Exception as exc:
|
|
2698
2874
|
actions.append(f"deep-sleep-heal-warning:{exc.__class__.__name__}")
|
|
2699
2875
|
|
|
2876
|
+
# Recover the user's legacy reasoning_effort preference into the new
|
|
2877
|
+
# default_resonance knob. v5.10.0 left this gap — users who had
|
|
2878
|
+
# `reasoning_effort="max"` in schedule.json silently degraded to
|
|
2879
|
+
# DEFAULT_RESONANCE=alto when the resonance map took over. This
|
|
2880
|
+
# migration restores their choice exactly once.
|
|
2881
|
+
try:
|
|
2882
|
+
_emit_progress(progress_fn, "Migrating legacy effort preference to resonance...")
|
|
2883
|
+
mig_actions = _migrate_effort_to_resonance(dest)
|
|
2884
|
+
for action in mig_actions:
|
|
2885
|
+
actions.append(action)
|
|
2886
|
+
except Exception as exc:
|
|
2887
|
+
actions.append(f"resonance-migration-warning:{exc.__class__.__name__}")
|
|
2888
|
+
|
|
2889
|
+
try:
|
|
2890
|
+
_emit_progress(progress_fn, "Bootstrapping profile.json from calibration...")
|
|
2891
|
+
boot_actions = _bootstrap_profile_from_calibration_meta(dest)
|
|
2892
|
+
for action in boot_actions:
|
|
2893
|
+
actions.append(action)
|
|
2894
|
+
except Exception as exc:
|
|
2895
|
+
actions.append(f"profile-bootstrap-warning:{exc.__class__.__name__}")
|
|
2896
|
+
|
|
2700
2897
|
_emit_progress(progress_fn, "Verifying runtime imports...")
|
|
2701
2898
|
verify = subprocess.run(
|
|
2702
2899
|
[sys.executable, "-c", "import server"],
|