nexo-brain 7.11.0 → 7.11.1
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 +1 -1
- package/package.json +1 -1
- package/src/runtime_versioning.py +120 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.11.
|
|
3
|
+
"version": "7.11.1",
|
|
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,7 @@
|
|
|
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.11.
|
|
21
|
+
Version `7.11.1` is the current packaged-runtime line. Patch release — caches the runtime fingerprint by `(file_count, size_total, max_mtime)` signature so MCP startup and the per-tool-call `resolve_restart_required` skip the 263-file rehash when nothing on disk changed. ~11× speedup warm path (~40ms → ~3.7ms locally), ~10-20s/day saved across Claude Code / Codex / headless / deep-sleep / cron startups. Cache miss is always safe (falls through to full hash and self-repairs). Default `use_cache=False` keeps `plugins/update.py` on the ground-truth path around `git pull` / `npm update`. Builds on the v7.11.0 runtime fingerprint that gates `mcp-restart-required.json`. Full write-up in [`docs/runtime-fingerprint.md`](docs/runtime-fingerprint.md).
|
|
22
22
|
|
|
23
23
|
Previously in `7.10.0`: minor release — **removes the LLM proxy override path that 7.9.28 → 7.9.34 introduced**. Background: 7.9.28 added two opt-in files at `~/.nexo/config/llm_endpoint.json` and `~/.nexo/config/auth_provider.json` that let a third-party orchestrator (NEXO Desktop) redirect every Anthropic SDK call from Brain to a custom proxy and resolve the bearer via a local helper, with concrete model names translated to wire aliases (`nexo-max`, `nexo-high`, `nexo-medium`, `nexo-low`, `nexo-mini`) and an `Idempotency-Key` per request. NEXO Desktop's commercial model has changed: Desktop is now a wrapper over the user's own Claude Code subscription (Max / Pro), with a separate Desktop licence. Brain calls go directly to `api.anthropic.com` using the user's existing OAuth (the one stored under `~/.claude/` and consumed by Claude Code spawns) or a plain `ANTHROPIC_API_KEY`. There is no NEXO bearer, no NEXO proxy, no NEXO credit accounting in this codebase. Every proxy symbol is gone from `call_model_raw.py` and `agent_runner.py`; the proxy-specific tests and `docs/api/override-files.md` are removed; any pre-existing override files on disk are simply ignored from this release forward.
|
|
24
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.11.
|
|
3
|
+
"version": "7.11.1",
|
|
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",
|
|
@@ -160,6 +160,98 @@ def restart_required_marker_path() -> Path:
|
|
|
160
160
|
return paths.operations_dir() / "mcp-restart-required.json"
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
def fingerprint_cache_path() -> Path:
|
|
164
|
+
"""Where the runtime fingerprint cache lives.
|
|
165
|
+
|
|
166
|
+
The cache lets `prime_process_fingerprint()` and `installed_runtime_fingerprint()`
|
|
167
|
+
skip hashing 200+ source files on every MCP startup / tool call when the
|
|
168
|
+
runtime tree on disk hasn't changed (same file count, same total size, same
|
|
169
|
+
max mtime). Invalidates automatically when any source byte changes.
|
|
170
|
+
"""
|
|
171
|
+
return paths.operations_dir() / "fingerprint-cache.json"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _runtime_tree_signature(src_dir: Path) -> tuple[int, int, float] | None:
|
|
175
|
+
"""Cheap stat-only walk over the fingerprint-tracked tree.
|
|
176
|
+
|
|
177
|
+
Returns ``(file_count, size_total, max_mtime)`` or ``None`` when the source
|
|
178
|
+
tree cannot be traversed. This is the cache key — if it matches, the bytes
|
|
179
|
+
haven't changed in any way the fingerprint would care about.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
files = _iter_runtime_source_files(src_dir)
|
|
183
|
+
except Exception:
|
|
184
|
+
return None
|
|
185
|
+
if not files:
|
|
186
|
+
return None
|
|
187
|
+
count = 0
|
|
188
|
+
size_total = 0
|
|
189
|
+
max_mtime = 0.0
|
|
190
|
+
for path in files:
|
|
191
|
+
try:
|
|
192
|
+
st = path.stat()
|
|
193
|
+
except Exception:
|
|
194
|
+
return None
|
|
195
|
+
count += 1
|
|
196
|
+
size_total += int(st.st_size)
|
|
197
|
+
if st.st_mtime > max_mtime:
|
|
198
|
+
max_mtime = float(st.st_mtime)
|
|
199
|
+
return (count, size_total, max_mtime)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _read_fingerprint_cache(src_dir: Path) -> str:
|
|
203
|
+
"""Return cached fingerprint when the on-disk signature still matches.
|
|
204
|
+
|
|
205
|
+
Empty string means cache miss (corrupt, missing, or signature drifted).
|
|
206
|
+
Cache miss is always safe — caller falls through to a full hash.
|
|
207
|
+
"""
|
|
208
|
+
cache_path = fingerprint_cache_path()
|
|
209
|
+
if not cache_path.is_file():
|
|
210
|
+
return ""
|
|
211
|
+
try:
|
|
212
|
+
payload = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
213
|
+
except Exception:
|
|
214
|
+
return ""
|
|
215
|
+
if not isinstance(payload, dict):
|
|
216
|
+
return ""
|
|
217
|
+
if str(payload.get("src_dir") or "") != str(src_dir):
|
|
218
|
+
return ""
|
|
219
|
+
sig = _runtime_tree_signature(src_dir)
|
|
220
|
+
if sig is None:
|
|
221
|
+
return ""
|
|
222
|
+
try:
|
|
223
|
+
cached_count = int(payload.get("file_count"))
|
|
224
|
+
cached_size = int(payload.get("size_total"))
|
|
225
|
+
cached_mtime = float(payload.get("max_mtime"))
|
|
226
|
+
except (TypeError, ValueError):
|
|
227
|
+
return ""
|
|
228
|
+
if cached_count != sig[0] or cached_size != sig[1] or cached_mtime != sig[2]:
|
|
229
|
+
return ""
|
|
230
|
+
fingerprint = str(payload.get("fingerprint") or "").strip()
|
|
231
|
+
return fingerprint
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _write_fingerprint_cache(src_dir: Path, fingerprint: str) -> None:
|
|
235
|
+
"""Persist the fingerprint+signature pair. Best-effort; failures don't propagate."""
|
|
236
|
+
if not fingerprint:
|
|
237
|
+
return
|
|
238
|
+
sig = _runtime_tree_signature(src_dir)
|
|
239
|
+
if sig is None:
|
|
240
|
+
return
|
|
241
|
+
payload = {
|
|
242
|
+
"fingerprint": fingerprint,
|
|
243
|
+
"src_dir": str(src_dir),
|
|
244
|
+
"file_count": sig[0],
|
|
245
|
+
"size_total": sig[1],
|
|
246
|
+
"max_mtime": sig[2],
|
|
247
|
+
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
248
|
+
}
|
|
249
|
+
try:
|
|
250
|
+
_write_json_atomic(fingerprint_cache_path(), payload)
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
|
|
163
255
|
def _candidate_version_files(base: Path) -> list[Path]:
|
|
164
256
|
return [
|
|
165
257
|
base / "version.json",
|
|
@@ -225,7 +317,9 @@ def _iter_runtime_source_files(src_dir: Path) -> list[Path]:
|
|
|
225
317
|
return out
|
|
226
318
|
|
|
227
319
|
|
|
228
|
-
def compute_mcp_runtime_fingerprint(
|
|
320
|
+
def compute_mcp_runtime_fingerprint(
|
|
321
|
+
src_dir: Path | None = None, *, use_cache: bool = False
|
|
322
|
+
) -> str:
|
|
229
323
|
"""Hash of every Python source file the running MCP can import.
|
|
230
324
|
|
|
231
325
|
Returns a sha256 hex digest, or "" when the source tree cannot be located
|
|
@@ -240,6 +334,14 @@ def compute_mcp_runtime_fingerprint(src_dir: Path | None = None) -> str:
|
|
|
240
334
|
* non-`.py` assets (docs, blogs, READMEs, JSON/YAML configs, templates,
|
|
241
335
|
CHANGELOG, marketing files) — these never affect what the live MCP
|
|
242
336
|
process executes
|
|
337
|
+
|
|
338
|
+
When ``use_cache=True`` (hot paths: server startup, every tool call) the
|
|
339
|
+
function consults ``fingerprint-cache.json``: if the on-disk tree
|
|
340
|
+
signature (file count + total size + max mtime) still matches the cached
|
|
341
|
+
one, the cached digest is returned without re-reading any byte. Cache miss
|
|
342
|
+
falls through to the normal full-hash path and writes a fresh entry. The
|
|
343
|
+
update flow keeps ``use_cache=False`` (default) so it always sees ground
|
|
344
|
+
truth around the pull/npm step.
|
|
243
345
|
"""
|
|
244
346
|
if src_dir is None:
|
|
245
347
|
candidates: list[Path] = []
|
|
@@ -267,6 +369,11 @@ def compute_mcp_runtime_fingerprint(src_dir: Path | None = None) -> str:
|
|
|
267
369
|
if src_dir is None:
|
|
268
370
|
return ""
|
|
269
371
|
|
|
372
|
+
if use_cache:
|
|
373
|
+
cached = _read_fingerprint_cache(src_dir)
|
|
374
|
+
if cached:
|
|
375
|
+
return cached
|
|
376
|
+
|
|
270
377
|
files = _iter_runtime_source_files(src_dir)
|
|
271
378
|
if not files:
|
|
272
379
|
return ""
|
|
@@ -283,11 +390,19 @@ def compute_mcp_runtime_fingerprint(src_dir: Path | None = None) -> str:
|
|
|
283
390
|
except Exception:
|
|
284
391
|
return ""
|
|
285
392
|
h.update(b"\n")
|
|
286
|
-
|
|
393
|
+
digest = h.hexdigest()
|
|
394
|
+
if use_cache and digest:
|
|
395
|
+
_write_fingerprint_cache(src_dir, digest)
|
|
396
|
+
return digest
|
|
287
397
|
|
|
288
398
|
|
|
289
399
|
def installed_runtime_fingerprint() -> str:
|
|
290
|
-
"""Fingerprint of whatever runtime source tree is on disk right now.
|
|
400
|
+
"""Fingerprint of whatever runtime source tree is on disk right now.
|
|
401
|
+
|
|
402
|
+
Hot path — runs on every MCP tool call via ``resolve_restart_required``.
|
|
403
|
+
Uses the disk-signature cache so a repeated call without any source
|
|
404
|
+
change is a few stat() syscalls instead of 200+ file reads.
|
|
405
|
+
"""
|
|
291
406
|
candidates: list[Path] = []
|
|
292
407
|
try:
|
|
293
408
|
root = active_runtime_root()
|
|
@@ -308,7 +423,7 @@ def installed_runtime_fingerprint() -> str:
|
|
|
308
423
|
except Exception:
|
|
309
424
|
pass
|
|
310
425
|
for cand in candidates:
|
|
311
|
-
fp = compute_mcp_runtime_fingerprint(cand)
|
|
426
|
+
fp = compute_mcp_runtime_fingerprint(cand, use_cache=True)
|
|
312
427
|
if fp:
|
|
313
428
|
return fp
|
|
314
429
|
return ""
|
|
@@ -616,7 +731,7 @@ def prime_process_fingerprint() -> str:
|
|
|
616
731
|
except Exception:
|
|
617
732
|
pass
|
|
618
733
|
for cand in candidates:
|
|
619
|
-
fp = compute_mcp_runtime_fingerprint(cand)
|
|
734
|
+
fp = compute_mcp_runtime_fingerprint(cand, use_cache=True)
|
|
620
735
|
if fp:
|
|
621
736
|
PROCESS_FINGERPRINT = fp
|
|
622
737
|
return PROCESS_FINGERPRINT
|