nexo-brain 7.9.4 → 7.9.5
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/README.md +3 -1
- package/package.json +1 -1
- package/src/lifecycle_events.py +59 -11
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.9.
|
|
21
|
+
Version `7.9.5` is the current packaged-runtime line. Patch release that fixes canonical diary confirmation for Desktop: Brain now resolves the Desktop/Claude session UUID through NEXO SID aliases before checking `session_diary`, so archive/delete/app-exit can confirm diaries written by `nexo_session_diary_write` under the active `nexo-...` SID. Verification: `pytest tests/test_lifecycle_events.py` (28 passing) plus coordinated Desktop v0.28.4 shutdown/archive/delete checks.
|
|
22
|
+
|
|
23
|
+
Previously in `7.9.4`: patch release that blocks the Brain 7.9.3 + Desktop 0.28.2 diary regression: canonical lifecycle plans now require real `session_diary` evidence (`wait_for_diary_write`) before `stop_session`, and canonical completion is rejected/retryable without that diary row. It also fixes npm CLI onboarding so `nexo-brain --version` and subcommands never launch the wizard when legacy/v2 calibration is already valid, commits setup calibration atomically only after the wizard completes, and adds `nexo-brain warmup-models` so install/update paths predownload the local mDeBERTa/BGE/reranker models. Verification: full Brain pytest (`2189 passed, 3 skipped, 1 xfailed, 5 xpassed`), release-readiness, npm pack dry-run, and coordinated Desktop v0.28.3 checks.
|
|
22
24
|
|
|
23
25
|
Previously in `7.9.3`: patch release that hardens Brain's canonical lifecycle plan for Desktop close/archive/delete/app-exit diary guarantees: `canonical_actions` now publish the v2 canonical shape (`type` plus `payload.prompt`) while keeping one-release compatibility mirrors (`kind` plus top-level `prompt`) for older Desktop clients. This lets Desktop execute resume → diary prompt → stop with one exact owner per lifecycle event and preserve Brain-side dedupe by event id. Targeted verification: `pytest tests/test_lifecycle_events.py` (25 passing) plus release-readiness after artifact sync.
|
|
24
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.5",
|
|
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/lifecycle_events.py
CHANGED
|
@@ -52,7 +52,7 @@ from __future__ import annotations
|
|
|
52
52
|
|
|
53
53
|
import json
|
|
54
54
|
import time
|
|
55
|
-
from typing import Any, Dict, Optional
|
|
55
|
+
from typing import Any, Dict, List, Optional
|
|
56
56
|
|
|
57
57
|
from db import get_db
|
|
58
58
|
import lifecycle_prompts
|
|
@@ -84,13 +84,56 @@ def _normalise_payload(obj: Any) -> str:
|
|
|
84
84
|
return "{}"
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def _session_diary_session_ids(conn, session_id: str) -> List[str]:
|
|
88
|
+
"""Return all session ids that can contain diary evidence for a lifecycle session.
|
|
89
|
+
|
|
90
|
+
Desktop passes Claude's conversation/session UUID as ``lifecycle_events.session_id``.
|
|
91
|
+
``nexo_session_diary_write`` stores rows under the active NEXO SID (``nexo-...``).
|
|
92
|
+
The alias table links both values, so canonical diary confirmation must check the
|
|
93
|
+
direct id and its NEXO aliases.
|
|
94
|
+
"""
|
|
95
|
+
raw = str(session_id or "").strip()
|
|
96
|
+
if not raw:
|
|
97
|
+
return []
|
|
98
|
+
ids: List[str] = [raw]
|
|
99
|
+
try:
|
|
100
|
+
rows = conn.execute(
|
|
101
|
+
"SELECT sid FROM session_claude_aliases "
|
|
102
|
+
"WHERE claude_session_id = ? ORDER BY last_seen DESC",
|
|
103
|
+
(raw,),
|
|
104
|
+
).fetchall()
|
|
105
|
+
ids.extend(str(row[0]) for row in rows if row and row[0])
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
try:
|
|
109
|
+
rows = conn.execute(
|
|
110
|
+
"SELECT sid FROM sessions "
|
|
111
|
+
"WHERE claude_session_id = ? OR external_session_id = ? "
|
|
112
|
+
"ORDER BY last_update_epoch DESC",
|
|
113
|
+
(raw, raw),
|
|
114
|
+
).fetchall()
|
|
115
|
+
ids.extend(str(row[0]) for row in rows if row and row[0])
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
deduped: List[str] = []
|
|
120
|
+
seen = set()
|
|
121
|
+
for sid in ids:
|
|
122
|
+
if sid and sid not in seen:
|
|
123
|
+
seen.add(sid)
|
|
124
|
+
deduped.append(sid)
|
|
125
|
+
return deduped
|
|
126
|
+
|
|
127
|
+
|
|
87
128
|
def _max_session_diary_id(conn, session_id: str) -> int:
|
|
88
|
-
|
|
129
|
+
session_ids = _session_diary_session_ids(conn, session_id)
|
|
130
|
+
if not session_ids:
|
|
89
131
|
return 0
|
|
132
|
+
placeholders = ",".join("?" for _ in session_ids)
|
|
90
133
|
try:
|
|
91
134
|
row = conn.execute(
|
|
92
|
-
"SELECT COALESCE(MAX(id), 0) FROM session_diary WHERE session_id
|
|
93
|
-
(
|
|
135
|
+
f"SELECT COALESCE(MAX(id), 0) FROM session_diary WHERE session_id IN ({placeholders})",
|
|
136
|
+
tuple(session_ids),
|
|
94
137
|
).fetchone()
|
|
95
138
|
except Exception:
|
|
96
139
|
return 0
|
|
@@ -156,25 +199,30 @@ def _session_diary_evidence(
|
|
|
156
199
|
if not session_id or not dispatched_at:
|
|
157
200
|
return None
|
|
158
201
|
checkpoint_id = _diary_checkpoint_from_actions_json(actions_json)
|
|
202
|
+
session_ids = _session_diary_session_ids(conn, session_id)
|
|
203
|
+
if not session_ids:
|
|
204
|
+
return None
|
|
205
|
+
placeholders = ",".join("?" for _ in session_ids)
|
|
159
206
|
try:
|
|
160
207
|
if checkpoint_id > 0:
|
|
161
208
|
row = conn.execute(
|
|
162
|
-
"SELECT id, created_at FROM session_diary "
|
|
163
|
-
"WHERE session_id
|
|
164
|
-
(
|
|
209
|
+
"SELECT id, created_at, session_id FROM session_diary "
|
|
210
|
+
f"WHERE session_id IN ({placeholders}) AND id > ? ORDER BY id ASC LIMIT 1",
|
|
211
|
+
(*session_ids, int(checkpoint_id)),
|
|
165
212
|
).fetchone()
|
|
166
213
|
else:
|
|
167
214
|
row = conn.execute(
|
|
168
|
-
"SELECT id, created_at FROM session_diary "
|
|
169
|
-
"WHERE session_id
|
|
170
|
-
|
|
215
|
+
"SELECT id, created_at, session_id FROM session_diary "
|
|
216
|
+
f"WHERE session_id IN ({placeholders}) AND created_at >= ? "
|
|
217
|
+
"ORDER BY created_at ASC, id ASC LIMIT 1",
|
|
218
|
+
(*session_ids, str(dispatched_at)),
|
|
171
219
|
).fetchone()
|
|
172
220
|
except Exception:
|
|
173
221
|
# Missing table on a minimal test harness — treat as "no diary".
|
|
174
222
|
return None
|
|
175
223
|
if row is None:
|
|
176
224
|
return None
|
|
177
|
-
return {"session_diary_id": row[0], "created_at": row[1]}
|
|
225
|
+
return {"session_diary_id": row[0], "created_at": row[1], "diary_session_id": row[2]}
|
|
178
226
|
|
|
179
227
|
|
|
180
228
|
def _session_diary_since(conn, session_id: str, dispatched_at: Optional[str], actions_json: Optional[str] = None) -> bool:
|