nexo-brain 2.6.16 → 2.6.17
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 +3 -1
- package/package.json +1 -1
- package/src/auto_update.py +10 -1
- package/src/bootstrap_docs.py +3 -1
- package/src/client_preferences.py +68 -2
- package/src/client_sync.py +4 -2
- package/src/plugins/update.py +10 -1
- package/src/scripts/nexo-update.sh +7 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.17",
|
|
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
|
@@ -38,11 +38,13 @@ That means NEXO now manages not only the shared runtime and MCP wiring, but also
|
|
|
38
38
|
- For Codex specifically, `nexo chat` and Codex headless automation inject the current bootstrap explicitly, so Codex starts as NEXO even when plain global Codex startup is inconsistent about global instructions.
|
|
39
39
|
- Deep Sleep now reads both Claude Code and Codex transcript stores, so overnight analysis still works even when the user spends the day in Codex.
|
|
40
40
|
|
|
41
|
-
Version `2.6.14` closes those parity gaps in practice, `2.6.15` hardens the installed-runtime migration path so existing users actually receive the managed bootstrap updates cleanly,
|
|
41
|
+
Version `2.6.14` closes those parity gaps in practice, `2.6.15` hardens the installed-runtime migration path so existing users actually receive the managed bootstrap updates cleanly, `2.6.16` pushes the system further in three directions, and `2.6.17` finishes the annoying last-mile migration bugs for real existing installs:
|
|
42
42
|
|
|
43
43
|
- Codex now gets managed global bootstrap/model sync in `~/.codex/config.toml`, so sessions opened outside `nexo chat` are much less likely to start as plain Codex.
|
|
44
44
|
- Retrieval is smarter by default: HyDE and spreading activation now auto-enable when the query shape benefits, while exact lookups remain conservative.
|
|
45
45
|
- Deep Sleep now blends recent context with older context over a 60-day horizon, and memory decay now tracks per-memory `stability` and `difficulty` instead of relying only on global decay constants.
|
|
46
|
+
- Existing installs that already had NEXO connected to Codex now backfill that client state automatically during update/sync, so the managed Codex bootstrap actually lands without manual cleanup.
|
|
47
|
+
- Bootstrap docs now fall back to the operator name `NEXO` when local metadata is blank, avoiding broken headings in `CLAUDE.md` and `AGENTS.md`.
|
|
46
48
|
|
|
47
49
|
### Client Capability Matrix
|
|
48
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.17",
|
|
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
|
@@ -1434,10 +1434,19 @@ def _run_runtime_post_sync(dest: Path = NEXO_HOME, progress_fn=None) -> tuple[bo
|
|
|
1434
1434
|
|
|
1435
1435
|
schedule_path = dest / "config" / "schedule.json"
|
|
1436
1436
|
schedule_payload = json.loads(schedule_path.read_text()) if schedule_path.exists() else {}
|
|
1437
|
+
normalized_preferences = normalize_client_preferences(schedule_payload)
|
|
1438
|
+
if normalized_preferences != {
|
|
1439
|
+
key: schedule_payload.get(key)
|
|
1440
|
+
for key in normalized_preferences
|
|
1441
|
+
}:
|
|
1442
|
+
merged_schedule = dict(schedule_payload)
|
|
1443
|
+
merged_schedule.update(normalized_preferences)
|
|
1444
|
+
schedule_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1445
|
+
schedule_path.write_text(json.dumps(merged_schedule, indent=2, ensure_ascii=False) + "\n")
|
|
1437
1446
|
client_sync_result = sync_all_clients(
|
|
1438
1447
|
nexo_home=dest,
|
|
1439
1448
|
runtime_root=dest,
|
|
1440
|
-
preferences=
|
|
1449
|
+
preferences=normalized_preferences,
|
|
1441
1450
|
)
|
|
1442
1451
|
if client_sync_result.get("ok"):
|
|
1443
1452
|
actions.append("client-sync")
|
package/src/bootstrap_docs.py
CHANGED
|
@@ -73,7 +73,9 @@ def _resolve_operator_name(nexo_home: Path, explicit: str = "") -> str:
|
|
|
73
73
|
version_file = nexo_home / "version.json"
|
|
74
74
|
if version_file.is_file():
|
|
75
75
|
try:
|
|
76
|
-
|
|
76
|
+
candidate = str(json.loads(version_file.read_text()).get("operator_name", "")).strip()
|
|
77
|
+
if candidate:
|
|
78
|
+
return candidate
|
|
77
79
|
except Exception:
|
|
78
80
|
pass
|
|
79
81
|
return "NEXO"
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
import shutil
|
|
7
7
|
import sys
|
|
8
|
+
import tomllib
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from runtime_power import load_schedule_config, save_schedule_config
|
|
@@ -51,6 +52,14 @@ def _user_home() -> Path:
|
|
|
51
52
|
return Path(os.environ.get("HOME", str(Path.home()))).expanduser()
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def _codex_config_path(home: Path) -> Path:
|
|
56
|
+
return home / ".codex" / "config.toml"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _codex_bootstrap_path(home: Path) -> Path:
|
|
60
|
+
return home / ".codex" / "AGENTS.md"
|
|
61
|
+
|
|
62
|
+
|
|
54
63
|
def _coerce_bool(value, default: bool) -> bool:
|
|
55
64
|
if isinstance(value, bool):
|
|
56
65
|
return value
|
|
@@ -127,6 +136,56 @@ def normalize_interactive_clients(value) -> dict[str, bool]:
|
|
|
127
136
|
return normalized
|
|
128
137
|
|
|
129
138
|
|
|
139
|
+
def _codex_artifacts_suggest_nexo_management(home: Path) -> bool:
|
|
140
|
+
bootstrap_path = _codex_bootstrap_path(home)
|
|
141
|
+
if bootstrap_path.is_file():
|
|
142
|
+
try:
|
|
143
|
+
bootstrap_text = bootstrap_path.read_text()
|
|
144
|
+
except Exception:
|
|
145
|
+
bootstrap_text = ""
|
|
146
|
+
if (
|
|
147
|
+
"nexo-codex-agents-version:" in bootstrap_text
|
|
148
|
+
or "NEXO Shared Brain for Codex" in bootstrap_text
|
|
149
|
+
or "<!-- nexo:core:start -->" in bootstrap_text
|
|
150
|
+
):
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
config_path = _codex_config_path(home)
|
|
154
|
+
if not config_path.is_file():
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
payload = tomllib.loads(config_path.read_text())
|
|
159
|
+
except Exception:
|
|
160
|
+
try:
|
|
161
|
+
raw_text = config_path.read_text()
|
|
162
|
+
except Exception:
|
|
163
|
+
return False
|
|
164
|
+
return "[mcp_servers.nexo]" in raw_text or "[nexo.codex]" in raw_text
|
|
165
|
+
|
|
166
|
+
if not isinstance(payload, dict):
|
|
167
|
+
return False
|
|
168
|
+
mcp_servers = payload.get("mcp_servers")
|
|
169
|
+
if isinstance(mcp_servers, dict) and "nexo" in mcp_servers:
|
|
170
|
+
return True
|
|
171
|
+
nexo_table = payload.get("nexo")
|
|
172
|
+
if isinstance(nexo_table, dict) and "codex" in nexo_table:
|
|
173
|
+
return True
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _backfill_interactive_clients(
|
|
178
|
+
interactive_clients: dict[str, bool],
|
|
179
|
+
*,
|
|
180
|
+
user_home: str | os.PathLike[str] | None = None,
|
|
181
|
+
) -> dict[str, bool]:
|
|
182
|
+
normalized = dict(interactive_clients)
|
|
183
|
+
home = Path(user_home).expanduser() if user_home else _user_home()
|
|
184
|
+
if not normalized.get(CLIENT_CODEX, False) and _codex_artifacts_suggest_nexo_management(home):
|
|
185
|
+
normalized[CLIENT_CODEX] = True
|
|
186
|
+
return normalized
|
|
187
|
+
|
|
188
|
+
|
|
130
189
|
def normalize_default_terminal_client(value, interactive_clients: dict[str, bool] | None = None) -> str:
|
|
131
190
|
interactive_clients = normalize_interactive_clients(interactive_clients or {})
|
|
132
191
|
candidate = normalize_client_key(value)
|
|
@@ -210,9 +269,16 @@ def normalize_client_runtime_profiles(value) -> dict[str, dict[str, str]]:
|
|
|
210
269
|
return normalized
|
|
211
270
|
|
|
212
271
|
|
|
213
|
-
def normalize_client_preferences(
|
|
272
|
+
def normalize_client_preferences(
|
|
273
|
+
schedule: dict | None = None,
|
|
274
|
+
*,
|
|
275
|
+
user_home: str | os.PathLike[str] | None = None,
|
|
276
|
+
) -> dict:
|
|
214
277
|
schedule = dict(schedule or {})
|
|
215
|
-
interactive_clients =
|
|
278
|
+
interactive_clients = _backfill_interactive_clients(
|
|
279
|
+
normalize_interactive_clients(schedule.get("interactive_clients")),
|
|
280
|
+
user_home=user_home,
|
|
281
|
+
)
|
|
216
282
|
automation_enabled = normalize_automation_enabled(schedule.get("automation_enabled"))
|
|
217
283
|
default_terminal_client = normalize_default_terminal_client(
|
|
218
284
|
schedule.get("default_terminal_client"),
|
package/src/client_sync.py
CHANGED
|
@@ -80,10 +80,12 @@ def _resolve_operator_name(nexo_home: Path, explicit: str = "") -> str:
|
|
|
80
80
|
version_file = nexo_home / "version.json"
|
|
81
81
|
if version_file.is_file():
|
|
82
82
|
try:
|
|
83
|
-
|
|
83
|
+
candidate = str(json.loads(version_file.read_text()).get("operator_name", "")).strip()
|
|
84
|
+
if candidate:
|
|
85
|
+
return candidate
|
|
84
86
|
except Exception:
|
|
85
87
|
pass
|
|
86
|
-
return ""
|
|
88
|
+
return "NEXO"
|
|
87
89
|
|
|
88
90
|
|
|
89
91
|
def _resolve_runtime_root(nexo_home: Path, runtime_root: str | os.PathLike[str] | None = None) -> Path:
|
package/src/plugins/update.py
CHANGED
|
@@ -592,11 +592,20 @@ def handle_update(remote: str = "origin", branch: str = "main", progress_fn=None
|
|
|
592
592
|
|
|
593
593
|
schedule_path = NEXO_HOME / "config" / "schedule.json"
|
|
594
594
|
schedule_payload = json.loads(schedule_path.read_text()) if schedule_path.exists() else {}
|
|
595
|
+
normalized_preferences = normalize_client_preferences(schedule_payload)
|
|
596
|
+
if normalized_preferences != {
|
|
597
|
+
key: schedule_payload.get(key)
|
|
598
|
+
for key in normalized_preferences
|
|
599
|
+
}:
|
|
600
|
+
merged_schedule = dict(schedule_payload)
|
|
601
|
+
merged_schedule.update(normalized_preferences)
|
|
602
|
+
schedule_path.parent.mkdir(parents=True, exist_ok=True)
|
|
603
|
+
schedule_path.write_text(json.dumps(merged_schedule, indent=2, ensure_ascii=False) + "\n")
|
|
595
604
|
client_sync_result = sync_all_clients(
|
|
596
605
|
nexo_home=NEXO_HOME,
|
|
597
606
|
runtime_root=SRC_DIR,
|
|
598
607
|
operator_name=os.environ.get("NEXO_NAME", ""),
|
|
599
|
-
preferences=
|
|
608
|
+
preferences=normalized_preferences,
|
|
600
609
|
)
|
|
601
610
|
if client_sync_result.get("ok"):
|
|
602
611
|
steps_done.append("client-sync")
|
|
@@ -266,7 +266,13 @@ sys.path.insert(0, os.environ["NEXO_CODE"])
|
|
|
266
266
|
from client_preferences import normalize_client_preferences
|
|
267
267
|
|
|
268
268
|
schedule_file = Path(os.environ["NEXO_HOME"]) / "config" / "schedule.json"
|
|
269
|
-
|
|
269
|
+
schedule_payload = json.loads(schedule_file.read_text()) if schedule_file.exists() else {}
|
|
270
|
+
prefs = normalize_client_preferences(schedule_payload)
|
|
271
|
+
if prefs != {key: schedule_payload.get(key) for key in prefs}:
|
|
272
|
+
merged = dict(schedule_payload)
|
|
273
|
+
merged.update(prefs)
|
|
274
|
+
schedule_file.parent.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
schedule_file.write_text(json.dumps(merged, indent=2, ensure_ascii=False) + "\n")
|
|
270
276
|
enabled = [key for key, value in prefs.get("interactive_clients", {}).items() if value]
|
|
271
277
|
if prefs.get("automation_enabled", True):
|
|
272
278
|
backend = prefs.get("automation_backend")
|