nexo-brain 7.37.0 → 7.37.3
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/db/_core.py +2 -1
- package/src/local_context/db.py +15 -0
- package/src/server.py +7 -2
- package/src/tools_sessions.py +23 -5
- package/tool-enforcement-map.json +13 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.37.
|
|
3
|
+
"version": "7.37.3",
|
|
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 `7.37.
|
|
21
|
+
Version `7.37.3` is the current packaged-runtime line. Patch release over v7.37.2 - release pipeline timeout hardening: the Brain publish workflow keeps the full pre-publish pytest gate, but now gives slow GitHub runners enough time to finish the suite and release readiness before public-channel publication.
|
|
22
|
+
|
|
23
|
+
Previously in `7.37.2`: patch release over v7.37.1 - runtime shutdown and CI stability: session keepalive writers now stop before the shared SQLite connection closes, SQLite close is serialized under the write lock, and the full Brain test workflow has enough time to finish instead of cancelling slow-but-valid runs. The v7.37.2 tag attempt did not publish npm/GitHub release artifacts; v7.37.3 is the public line.
|
|
24
|
+
|
|
25
|
+
Previously in `7.37.1`: patch release over v7.37.0 - release hardening for Desktop-bundled Brain: large existing `local-context.db` files no longer run a surprise full `VACUUM` on the first writer, `schema_abstraction` MCP tools are loaded by the essential startup set, and learning tools tolerate Desktop compatibility payloads. Builds on v7.37.0 (transparent server self-heal + email zombie reinjection guard).
|
|
22
26
|
|
|
23
27
|
Previously in `7.31.9`: patch release over v7.31.8 - UI release closeout now has to prove the original reported symptom was reopened with observable evidence before claiming the release is ready.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.37.
|
|
3
|
+
"version": "7.37.3",
|
|
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/db/_core.py
CHANGED
package/src/local_context/db.py
CHANGED
|
@@ -20,6 +20,8 @@ MAIN_CLEANUP_STATE_KEY = "local_context_main_tables_drained"
|
|
|
20
20
|
# per never-converted DB (guarded by free disk) and record it here so it never
|
|
21
21
|
# re-runs the expensive rewrite. See ensure_local_context_db().
|
|
22
22
|
AUTO_VACUUM_CONVERTED_KEY = "auto_vacuum_converted"
|
|
23
|
+
AUTO_VACUUM_MAX_SYNC_BYTES_ENV = "NEXO_LOCAL_CONTEXT_AUTO_VACUUM_MAX_SYNC_BYTES"
|
|
24
|
+
DEFAULT_AUTO_VACUUM_MAX_SYNC_BYTES = 128 * 1024 * 1024
|
|
23
25
|
|
|
24
26
|
LOCAL_CONTEXT_TABLES: tuple[str, ...] = (
|
|
25
27
|
"local_index_roots",
|
|
@@ -472,6 +474,9 @@ def _convert_auto_vacuum_once(conn: sqlite3.Connection, db_path: Path) -> None:
|
|
|
472
474
|
free = shutil.disk_usage(db_path.parent).free
|
|
473
475
|
except OSError:
|
|
474
476
|
return
|
|
477
|
+
max_sync_bytes = _auto_vacuum_max_sync_bytes()
|
|
478
|
+
if max_sync_bytes <= 0 or db_size > max_sync_bytes:
|
|
479
|
+
return
|
|
475
480
|
if free <= db_size * 2:
|
|
476
481
|
# Not enough scratch room — leave NONE mode, retry on a later boot.
|
|
477
482
|
return
|
|
@@ -486,6 +491,16 @@ def _convert_auto_vacuum_once(conn: sqlite3.Connection, db_path: Path) -> None:
|
|
|
486
491
|
pass
|
|
487
492
|
|
|
488
493
|
|
|
494
|
+
def _auto_vacuum_max_sync_bytes() -> int:
|
|
495
|
+
value = os.environ.get(AUTO_VACUUM_MAX_SYNC_BYTES_ENV, "")
|
|
496
|
+
if value:
|
|
497
|
+
try:
|
|
498
|
+
return max(0, int(value))
|
|
499
|
+
except (TypeError, ValueError):
|
|
500
|
+
return DEFAULT_AUTO_VACUUM_MAX_SYNC_BYTES
|
|
501
|
+
return DEFAULT_AUTO_VACUUM_MAX_SYNC_BYTES
|
|
502
|
+
|
|
503
|
+
|
|
489
504
|
def get_local_context_db() -> sqlite3.Connection:
|
|
490
505
|
ensure_local_context_db()
|
|
491
506
|
assert _CONN is not None
|
package/src/server.py
CHANGED
|
@@ -340,6 +340,7 @@ _ESSENTIAL_MCP_STARTUP_PLUGINS = (
|
|
|
340
340
|
"preferences.py",
|
|
341
341
|
"protocol.py",
|
|
342
342
|
"recover.py",
|
|
343
|
+
"schema_abstraction.py",
|
|
343
344
|
"skills.py",
|
|
344
345
|
"user_state_tools.py",
|
|
345
346
|
"workflow.py",
|
|
@@ -2630,12 +2631,14 @@ def nexo_learning_resolve_candidate(
|
|
|
2630
2631
|
|
|
2631
2632
|
|
|
2632
2633
|
@mcp.tool
|
|
2633
|
-
def nexo_learning_search(query: str, category: str = "") -> str:
|
|
2634
|
+
def nexo_learning_search(query: str, category: str = "", limit: int = 0) -> str:
|
|
2634
2635
|
"""Search learnings by keyword. Searches title and content.
|
|
2635
2636
|
|
|
2636
2637
|
Args:
|
|
2637
2638
|
query: Search term.
|
|
2638
2639
|
category: Filter by category (optional).
|
|
2640
|
+
limit: Accepted for Desktop/client compatibility; search ranking is
|
|
2641
|
+
owned by the Brain handler.
|
|
2639
2642
|
"""
|
|
2640
2643
|
return handle_learning_search(query, category)
|
|
2641
2644
|
|
|
@@ -2767,13 +2770,15 @@ def nexo_learning_delete(id: int) -> str:
|
|
|
2767
2770
|
|
|
2768
2771
|
|
|
2769
2772
|
@mcp.tool
|
|
2770
|
-
def nexo_learning_list(category: str = "", created_after: str = "", created_before: str = "") -> str:
|
|
2773
|
+
def nexo_learning_list(category: str = "", created_after: str = "", created_before: str = "", limit: int = 0) -> str:
|
|
2771
2774
|
"""List all learnings, grouped by category.
|
|
2772
2775
|
|
|
2773
2776
|
Args:
|
|
2774
2777
|
category: Filter by category (optional). If empty, shows all grouped.
|
|
2775
2778
|
created_after: Filter to learnings created at or after this date/time (optional).
|
|
2776
2779
|
created_before: Filter to learnings created at or before this date/time (optional).
|
|
2780
|
+
limit: Accepted for Desktop/client compatibility; grouping is owned by
|
|
2781
|
+
the Brain handler.
|
|
2777
2782
|
"""
|
|
2778
2783
|
return handle_learning_list(category, created_after, created_before)
|
|
2779
2784
|
|
package/src/tools_sessions.py
CHANGED
|
@@ -111,7 +111,7 @@ def _safe_packet_payload(value, *, _depth: int = 0):
|
|
|
111
111
|
return [_safe_packet_payload(item, _depth=_depth + 1) for item in list(value)[:100]]
|
|
112
112
|
return _safe_packet_text(value)
|
|
113
113
|
|
|
114
|
-
_keepalive_threads: dict[str, threading.Event] = {} # sid
|
|
114
|
+
_keepalive_threads: dict[str, tuple[threading.Event, threading.Thread]] = {} # sid -> (stop_event, thread)
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def _env_flag(name: str, default: bool = False) -> bool:
|
|
@@ -235,16 +235,34 @@ def _start_keepalive(sid: str) -> None:
|
|
|
235
235
|
"""Start a keepalive thread for the given session."""
|
|
236
236
|
_stop_keepalive(sid) # clean up any leftover
|
|
237
237
|
stop_event = threading.Event()
|
|
238
|
-
_keepalive_threads[sid] = stop_event
|
|
239
238
|
t = threading.Thread(target=_keepalive_loop, args=(sid, stop_event), daemon=True)
|
|
239
|
+
_keepalive_threads[sid] = (stop_event, t)
|
|
240
240
|
t.start()
|
|
241
241
|
|
|
242
242
|
|
|
243
|
-
def _stop_keepalive(sid: str) -> None:
|
|
243
|
+
def _stop_keepalive(sid: str, join_timeout: float = 1.0) -> None:
|
|
244
244
|
"""Signal the keepalive thread for the given session to stop."""
|
|
245
|
-
|
|
246
|
-
if
|
|
245
|
+
entry = _keepalive_threads.pop(sid, None)
|
|
246
|
+
if entry is None:
|
|
247
|
+
return
|
|
248
|
+
stop_event, thread = entry
|
|
249
|
+
stop_event.set()
|
|
250
|
+
if thread is not threading.current_thread():
|
|
251
|
+
thread.join(timeout=max(0.0, join_timeout))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _stop_all_keepalives(join_timeout: float = 1.0) -> None:
|
|
255
|
+
"""Signal and briefly join all keepalive threads before DB shutdown."""
|
|
256
|
+
entries = list(_keepalive_threads.values())
|
|
257
|
+
_keepalive_threads.clear()
|
|
258
|
+
for stop_event, _thread in entries:
|
|
247
259
|
stop_event.set()
|
|
260
|
+
deadline = time.monotonic() + max(0.0, join_timeout)
|
|
261
|
+
for _stop_event, thread in entries:
|
|
262
|
+
if thread is threading.current_thread():
|
|
263
|
+
continue
|
|
264
|
+
remaining = max(0.0, deadline - time.monotonic())
|
|
265
|
+
thread.join(timeout=remaining)
|
|
248
266
|
|
|
249
267
|
|
|
250
268
|
def _generate_sid() -> str:
|
|
@@ -3283,6 +3283,19 @@
|
|
|
3283
3283
|
},
|
|
3284
3284
|
"triggers_after": []
|
|
3285
3285
|
},
|
|
3286
|
+
"nexo_memory_forget": {
|
|
3287
|
+
"description": "Selective forget for revoked secrets or reversible fact correction",
|
|
3288
|
+
"category": "memory",
|
|
3289
|
+
"source": "server",
|
|
3290
|
+
"requires": [],
|
|
3291
|
+
"provides": [],
|
|
3292
|
+
"internal_calls": [],
|
|
3293
|
+
"enforcement": {
|
|
3294
|
+
"level": "none",
|
|
3295
|
+
"rules": []
|
|
3296
|
+
},
|
|
3297
|
+
"triggers_after": []
|
|
3298
|
+
},
|
|
3286
3299
|
"nexo_memory_health": {
|
|
3287
3300
|
"description": "Report Memory Observations v2 health and table status",
|
|
3288
3301
|
"category": "memory",
|