nexo-brain 7.20.23 → 7.20.24
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/local_context/api.py +36 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.24",
|
|
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.20.
|
|
21
|
+
Version `7.20.24` is the current packaged-runtime line. Patch release over v7.20.23 — Local Memory performance profile writes now tolerate active indexing, retry transient SQLite busy states, and shorten indexer write locks between processed files.
|
|
22
|
+
|
|
23
|
+
Previously in `7.20.23`: patch release over v7.20.22 — Local Memory status reads the real split sidecar database read-only, reports retryable keyed failures without false zeroes, and keeps Desktop Spanish/English copy localized.
|
|
22
24
|
|
|
23
25
|
Previously in `7.20.22`: patch release over v7.20.19 — Local Memory moved out of the main Brain database, MCP readiness verifies required tools, and split-aware Desktop backups validate the main DB and Local Memory sidecar separately.
|
|
24
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.20.
|
|
3
|
+
"version": "7.20.24",
|
|
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/local_context/api.py
CHANGED
|
@@ -9,11 +9,12 @@ import stat
|
|
|
9
9
|
import hashlib
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
|
+
import time
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
15
16
|
from . import embeddings
|
|
16
|
-
from .db import LOCAL_CONTEXT_TABLES, connect_local_context_db_readonly, ensure_local_context_db, get_local_context_db
|
|
17
|
+
from .db import LOCAL_CONTEXT_TABLES, close_local_context_db, connect_local_context_db_readonly, ensure_local_context_db, get_local_context_db
|
|
17
18
|
from .extractors import chunk_text, contains_secret, entities, extract_text, summarize
|
|
18
19
|
from .logging import log_event, tail
|
|
19
20
|
from .privacy import classify_path, is_local_email_tree, is_queryable_path, should_extract, should_skip_file, should_skip_tree
|
|
@@ -33,6 +34,8 @@ DEFAULT_SYSTEM_ROOT_DEPTH = int(os.environ.get("NEXO_LOCAL_INDEX_SYSTEM_ROOT_DEP
|
|
|
33
34
|
DEFAULT_CONTEXT_MAX_CHARS = int(os.environ.get("NEXO_LOCAL_CONTEXT_MAX_CHARS", "20000") or "20000")
|
|
34
35
|
DEFAULT_ROUTER_MAX_CHARS = int(os.environ.get("NEXO_LOCAL_CONTEXT_ROUTER_MAX_CHARS", "6000") or "6000")
|
|
35
36
|
DEFAULT_MAX_JOB_ATTEMPTS = int(os.environ.get("NEXO_LOCAL_INDEX_MAX_JOB_ATTEMPTS", "3") or "3")
|
|
37
|
+
DEFAULT_SQLITE_BUSY_RETRY_ATTEMPTS = int(os.environ.get("NEXO_LOCAL_CONTEXT_BUSY_RETRY_ATTEMPTS", "5") or "5")
|
|
38
|
+
DEFAULT_SQLITE_BUSY_RETRY_DELAY_SECONDS = float(os.environ.get("NEXO_LOCAL_CONTEXT_BUSY_RETRY_DELAY_SECONDS", "0.35") or "0.35")
|
|
36
39
|
INITIAL_INDEX_COMPLETE_KEY = "initial_index_complete"
|
|
37
40
|
INITIAL_INDEX_STARTED_AT_KEY = "initial_index_started_at"
|
|
38
41
|
PERFORMANCE_PROFILE_KEY = "performance_profile"
|
|
@@ -108,6 +111,27 @@ def _close_read_conn(conn) -> None:
|
|
|
108
111
|
pass
|
|
109
112
|
|
|
110
113
|
|
|
114
|
+
def _sqlite_is_busy(exc: BaseException) -> bool:
|
|
115
|
+
return isinstance(exc, sqlite3.OperationalError) and "locked" in str(exc).lower()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _with_sqlite_busy_retry(callback, *, attempts: int | None = None):
|
|
119
|
+
max_attempts = max(1, int(attempts or DEFAULT_SQLITE_BUSY_RETRY_ATTEMPTS))
|
|
120
|
+
last_exc = None
|
|
121
|
+
for attempt in range(max_attempts):
|
|
122
|
+
try:
|
|
123
|
+
return callback()
|
|
124
|
+
except sqlite3.OperationalError as exc:
|
|
125
|
+
if not _sqlite_is_busy(exc) or attempt >= max_attempts - 1:
|
|
126
|
+
raise
|
|
127
|
+
last_exc = exc
|
|
128
|
+
close_local_context_db()
|
|
129
|
+
time.sleep(DEFAULT_SQLITE_BUSY_RETRY_DELAY_SECONDS * (attempt + 1))
|
|
130
|
+
if last_exc:
|
|
131
|
+
raise last_exc
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
111
135
|
def add_root(path: str, *, mode: str = "normal", depth: int | None = None) -> dict:
|
|
112
136
|
conn = _conn()
|
|
113
137
|
root_path = norm_path(path)
|
|
@@ -609,9 +633,12 @@ def _set_state_conn(conn, key: str, value: str) -> None:
|
|
|
609
633
|
|
|
610
634
|
|
|
611
635
|
def _set_state(key: str, value: str) -> None:
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
636
|
+
def write_state() -> None:
|
|
637
|
+
conn = _conn()
|
|
638
|
+
_set_state_conn(conn, key, value)
|
|
639
|
+
conn.commit()
|
|
640
|
+
|
|
641
|
+
_with_sqlite_busy_retry(write_state)
|
|
615
642
|
|
|
616
643
|
|
|
617
644
|
def _get_state_conn(conn, key: str, default: str = "") -> str:
|
|
@@ -1745,6 +1772,7 @@ def process_jobs(*, limit: int = 100) -> dict:
|
|
|
1745
1772
|
"UPDATE local_index_jobs SET status='running', claimed_by='local-process', lease_expires_at=?, updated_at=? WHERE job_id=?",
|
|
1746
1773
|
(now() + 300, now(), job_id),
|
|
1747
1774
|
)
|
|
1775
|
+
conn.commit()
|
|
1748
1776
|
try:
|
|
1749
1777
|
if row["asset_status"] != "active":
|
|
1750
1778
|
raise FileNotFoundError(row["path"])
|
|
@@ -1754,6 +1782,7 @@ def process_jobs(*, limit: int = 100) -> dict:
|
|
|
1754
1782
|
(now(), job_id),
|
|
1755
1783
|
)
|
|
1756
1784
|
processed += 1
|
|
1785
|
+
conn.commit()
|
|
1757
1786
|
continue
|
|
1758
1787
|
if job_type == "light_extraction":
|
|
1759
1788
|
text, metadata = extract_text(Path(row["path"]))
|
|
@@ -1765,6 +1794,7 @@ def process_jobs(*, limit: int = 100) -> dict:
|
|
|
1765
1794
|
(now(), job_id),
|
|
1766
1795
|
)
|
|
1767
1796
|
processed += 1
|
|
1797
|
+
conn.commit()
|
|
1768
1798
|
continue
|
|
1769
1799
|
summary = summarize(text)
|
|
1770
1800
|
conn.execute(
|
|
@@ -1787,6 +1817,7 @@ def process_jobs(*, limit: int = 100) -> dict:
|
|
|
1787
1817
|
(now(), job_id),
|
|
1788
1818
|
)
|
|
1789
1819
|
processed += 1
|
|
1820
|
+
conn.commit()
|
|
1790
1821
|
except Exception as exc:
|
|
1791
1822
|
failed += 1
|
|
1792
1823
|
attempts = int(row["attempt_count"] or 0) + 1
|
|
@@ -1809,6 +1840,7 @@ def process_jobs(*, limit: int = 100) -> dict:
|
|
|
1809
1840
|
technical_detail=str(exc),
|
|
1810
1841
|
retryable=not terminal,
|
|
1811
1842
|
)
|
|
1843
|
+
conn.commit()
|
|
1812
1844
|
conn.commit()
|
|
1813
1845
|
if processed or failed:
|
|
1814
1846
|
log_event("info", "jobs_processed", "Local memory jobs processed", processed=processed, failed=failed)
|