nexo-brain 7.9.23 → 7.9.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/lifecycle_events.py +106 -0
- package/src/plugins/lifecycle_events.py +6 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.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.9.
|
|
21
|
+
Version `7.9.24` is the current packaged-runtime line. Patch release over `7.9.23`: Desktop lifecycle shutdown now resolves alias-only diary SIDs back to the registered NEXO session before stopping, so app-exit can preserve the diary and confirm the real session is closed.
|
|
22
|
+
|
|
23
|
+
Previously in `7.9.23`: Desktop lifecycle fallback diaries now enrich sparse lifecycle events from continuity snapshots, so app-exit fallback evidence preserves recent turn context even when the live agent does not answer the injected diary prompt before shutdown.
|
|
22
24
|
|
|
23
25
|
Previously in `7.9.22`: Desktop lifecycle shutdowns gained an emergency Brain-side fallback diary path, so close/archive/app-exit can preserve title, goal, session ids, and transcript tail even when the live agent does not answer the injected diary prompt before shutdown.
|
|
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.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/lifecycle_events.py
CHANGED
|
@@ -127,6 +127,109 @@ def _session_diary_session_ids(conn, session_id: str) -> List[str]:
|
|
|
127
127
|
return deduped
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def _registered_session_ids(conn, session_id: str, candidates: Optional[List[str]] = None) -> List[str]:
|
|
131
|
+
"""Return real active-session table ids linked to a lifecycle session id."""
|
|
132
|
+
raw = str(session_id or "").strip()
|
|
133
|
+
ids = [str(s or "").strip() for s in list(candidates or []) if str(s or "").strip()]
|
|
134
|
+
if raw and raw not in ids:
|
|
135
|
+
ids.append(raw)
|
|
136
|
+
rows = []
|
|
137
|
+
try:
|
|
138
|
+
if ids:
|
|
139
|
+
placeholders = ",".join("?" for _ in ids)
|
|
140
|
+
rows = conn.execute(
|
|
141
|
+
"SELECT sid FROM sessions "
|
|
142
|
+
f"WHERE sid IN ({placeholders}) OR external_session_id = ? OR claude_session_id = ? "
|
|
143
|
+
"ORDER BY last_update_epoch DESC",
|
|
144
|
+
(*ids, raw, raw),
|
|
145
|
+
).fetchall()
|
|
146
|
+
elif raw:
|
|
147
|
+
rows = conn.execute(
|
|
148
|
+
"SELECT sid FROM sessions "
|
|
149
|
+
"WHERE external_session_id = ? OR claude_session_id = ? OR sid = ? "
|
|
150
|
+
"ORDER BY last_update_epoch DESC",
|
|
151
|
+
(raw, raw, raw),
|
|
152
|
+
).fetchall()
|
|
153
|
+
except Exception:
|
|
154
|
+
rows = []
|
|
155
|
+
|
|
156
|
+
deduped: List[str] = []
|
|
157
|
+
seen = set()
|
|
158
|
+
for row in rows:
|
|
159
|
+
sid = str(row[0] or "").strip() if row else ""
|
|
160
|
+
if sid and sid not in seen:
|
|
161
|
+
seen.add(sid)
|
|
162
|
+
deduped.append(sid)
|
|
163
|
+
return deduped
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _linked_external_session_ids(conn, session_id: str) -> List[str]:
|
|
167
|
+
"""Return external Claude/Desktop ids linked to a NEXO SID or raw session id."""
|
|
168
|
+
raw = str(session_id or "").strip()
|
|
169
|
+
if not raw:
|
|
170
|
+
return []
|
|
171
|
+
ids: List[str] = []
|
|
172
|
+
if not raw.startswith("nexo-"):
|
|
173
|
+
ids.append(raw)
|
|
174
|
+
try:
|
|
175
|
+
rows = conn.execute(
|
|
176
|
+
"SELECT claude_session_id FROM session_claude_aliases WHERE sid = ? "
|
|
177
|
+
"ORDER BY last_seen DESC",
|
|
178
|
+
(raw,),
|
|
179
|
+
).fetchall()
|
|
180
|
+
ids.extend(str(row[0] or "").strip() for row in rows if row and row[0])
|
|
181
|
+
except Exception:
|
|
182
|
+
pass
|
|
183
|
+
try:
|
|
184
|
+
rows = conn.execute(
|
|
185
|
+
"SELECT external_session_id, claude_session_id FROM sessions WHERE sid = ?",
|
|
186
|
+
(raw,),
|
|
187
|
+
).fetchall()
|
|
188
|
+
for row in rows:
|
|
189
|
+
ids.extend(str(value or "").strip() for value in row if value)
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
deduped: List[str] = []
|
|
194
|
+
seen = set()
|
|
195
|
+
for external_id in ids:
|
|
196
|
+
if external_id and external_id not in seen:
|
|
197
|
+
seen.add(external_id)
|
|
198
|
+
deduped.append(external_id)
|
|
199
|
+
return deduped
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _registered_stop_session_ids(conn, session_id: str) -> List[str]:
|
|
203
|
+
"""Resolve a lifecycle stop target to real registered NEXO SIDs."""
|
|
204
|
+
raw = str(session_id or "").strip()
|
|
205
|
+
if not raw:
|
|
206
|
+
return []
|
|
207
|
+
candidates = _session_diary_session_ids(conn, raw)
|
|
208
|
+
external_ids = _linked_external_session_ids(conn, raw)
|
|
209
|
+
for external_id in external_ids:
|
|
210
|
+
candidates.extend(_session_diary_session_ids(conn, external_id))
|
|
211
|
+
|
|
212
|
+
registered: List[str] = []
|
|
213
|
+
registered.extend(_registered_session_ids(conn, raw, candidates))
|
|
214
|
+
for external_id in external_ids:
|
|
215
|
+
registered.extend(_registered_session_ids(conn, external_id, candidates))
|
|
216
|
+
|
|
217
|
+
deduped: List[str] = []
|
|
218
|
+
seen = set()
|
|
219
|
+
for sid in registered:
|
|
220
|
+
if sid and sid not in seen:
|
|
221
|
+
seen.add(sid)
|
|
222
|
+
deduped.append(sid)
|
|
223
|
+
if deduped:
|
|
224
|
+
return deduped
|
|
225
|
+
return [raw] if raw.startswith("nexo-") else []
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def registered_stop_session_ids(session_id: str) -> List[str]:
|
|
229
|
+
"""Public wrapper used by plugin handlers before calling nexo_stop."""
|
|
230
|
+
return _registered_stop_session_ids(get_db(), session_id)
|
|
231
|
+
|
|
232
|
+
|
|
130
233
|
def _session_is_linked_to_nexo(conn, session_id: str) -> bool:
|
|
131
234
|
"""True when the external Claude/Desktop session is linked to a NEXO SID."""
|
|
132
235
|
raw = str(session_id or "").strip()
|
|
@@ -264,6 +367,9 @@ def _preferred_diary_session_id(conn, session_id: str) -> str:
|
|
|
264
367
|
"""Return the best session id to store fallback diary evidence under."""
|
|
265
368
|
raw = str(session_id or "").strip()
|
|
266
369
|
candidates = _session_diary_session_ids(conn, raw)
|
|
370
|
+
for sid in _registered_session_ids(conn, raw, candidates):
|
|
371
|
+
if str(sid or "").startswith("nexo-"):
|
|
372
|
+
return str(sid)
|
|
267
373
|
for sid in candidates:
|
|
268
374
|
if str(sid or "").startswith("nexo-"):
|
|
269
375
|
return str(sid)
|
|
@@ -217,11 +217,14 @@ def handle_nexo_lifecycle_stop_nexo_session(
|
|
|
217
217
|
try:
|
|
218
218
|
from tools_sessions import handle_stop
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
raw_sid = str(sid or "").strip()
|
|
221
|
+
stop_sids = lifecycle_events.registered_stop_session_ids(raw_sid) or [raw_sid]
|
|
222
|
+
messages = [handle_stop(stop_sid) for stop_sid in stop_sids]
|
|
221
223
|
return json.dumps({
|
|
222
224
|
"status": "ok",
|
|
223
|
-
"sid":
|
|
224
|
-
"
|
|
225
|
+
"sid": raw_sid,
|
|
226
|
+
"stopped_session_ids": stop_sids,
|
|
227
|
+
"message": " ".join(messages),
|
|
225
228
|
}, ensure_ascii=False)
|
|
226
229
|
except Exception as exc:
|
|
227
230
|
return json.dumps({
|