nexo-brain 7.9.5 → 7.9.6
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/bin/nexo-brain.js +9 -0
- package/package.json +1 -1
- package/src/auto_update.py +9 -0
- package/src/cli.py +138 -0
- package/src/continuity.py +442 -0
- package/src/db/__init__.py +8 -0
- package/src/db/_continuity.py +171 -0
- package/src/db/_schema.py +33 -0
- package/src/db/_sessions.py +23 -7
- package/src/paths.py +11 -1
- package/src/plugins/update.py +49 -0
- package/src/runtime_versioning.py +342 -0
- package/src/server.py +103 -1
- package/src/tools_sessions.py +30 -0
- package/tool-enforcement-map.json +75 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.6",
|
|
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.6` is the current packaged-runtime line. Patch release that closes continuity + MCP restart stability for installed users: Brain now persists conversation-scoped continuity snapshots and resume bundles, activates packaged runtimes atomically under `~/.nexo/core/current`, writes a durable `mcp-restart-required` marker on real version changes, and self-drains old MCP processes until the client restarts against the installed runtime. Coordinated Desktop v0.28.7 consumes the same contract so existing `brain-only` and `brain+desktop` installs reroute to the new runtime instead of running mixed old/new MCP code.
|
|
22
|
+
|
|
23
|
+
Previously in `7.9.5`: patch release that fixes canonical diary confirmation for Desktop: Brain 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.6 shutdown/archive/delete/app-exit checks.
|
|
22
24
|
|
|
23
25
|
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.
|
|
24
26
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -3249,6 +3249,10 @@ async function runSetup() {
|
|
|
3249
3249
|
' printf \'%s\\n\' "${NEXO_CODE%/}"',
|
|
3250
3250
|
' return 0',
|
|
3251
3251
|
' fi',
|
|
3252
|
+
' if [ -f "$NEXO_HOME/core/current/cli.py" ]; then',
|
|
3253
|
+
' printf \'%s\\n\' "$NEXO_HOME/core/current"',
|
|
3254
|
+
' return 0',
|
|
3255
|
+
' fi',
|
|
3252
3256
|
' if [ -f "$NEXO_HOME/core/cli.py" ]; then',
|
|
3253
3257
|
' printf \'%s\\n\' "$NEXO_HOME/core"',
|
|
3254
3258
|
' return 0',
|
|
@@ -3302,6 +3306,11 @@ async function runSetup() {
|
|
|
3302
3306
|
' exit 1',
|
|
3303
3307
|
'fi',
|
|
3304
3308
|
'CLI_PY="$NEXO_CODE/cli.py"',
|
|
3309
|
+
'if [ ! -f "$CLI_PY" ] && [ -f "$NEXO_HOME/core/current/cli.py" ]; then',
|
|
3310
|
+
' NEXO_CODE="$NEXO_HOME/core/current"',
|
|
3311
|
+
' export NEXO_CODE',
|
|
3312
|
+
' CLI_PY="$NEXO_HOME/core/current/cli.py"',
|
|
3313
|
+
'fi',
|
|
3305
3314
|
'if [ ! -f "$CLI_PY" ] && [ -f "$NEXO_HOME/core/cli.py" ]; then',
|
|
3306
3315
|
' NEXO_CODE="$NEXO_HOME/core"',
|
|
3307
3316
|
' export NEXO_CODE',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.6",
|
|
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/auto_update.py
CHANGED
|
@@ -537,6 +537,10 @@ def _runtime_cli_wrapper_text() -> str:
|
|
|
537
537
|
' printf \'%s\\n\' "${NEXO_CODE%/}"\n'
|
|
538
538
|
' return 0\n'
|
|
539
539
|
' fi\n'
|
|
540
|
+
' if [ -f "$NEXO_HOME/core/current/cli.py" ]; then\n'
|
|
541
|
+
' printf \'%s\\n\' "$NEXO_HOME/core/current"\n'
|
|
542
|
+
' return 0\n'
|
|
543
|
+
' fi\n'
|
|
540
544
|
' if [ -f "$NEXO_HOME/core/cli.py" ]; then\n'
|
|
541
545
|
' printf \'%s\\n\' "$NEXO_HOME/core"\n'
|
|
542
546
|
' return 0\n'
|
|
@@ -590,6 +594,11 @@ def _runtime_cli_wrapper_text() -> str:
|
|
|
590
594
|
' exit 1\n'
|
|
591
595
|
'fi\n'
|
|
592
596
|
'CLI_PY="$NEXO_CODE/cli.py"\n'
|
|
597
|
+
'if [ ! -f "$CLI_PY" ] && [ -f "$NEXO_HOME/core/current/cli.py" ]; then\n'
|
|
598
|
+
' NEXO_CODE="$NEXO_HOME/core/current"\n'
|
|
599
|
+
' export NEXO_CODE\n'
|
|
600
|
+
' CLI_PY="$NEXO_HOME/core/current/cli.py"\n'
|
|
601
|
+
'fi\n'
|
|
593
602
|
'if [ ! -f "$CLI_PY" ] && [ -f "$NEXO_HOME/core/cli.py" ]; then\n'
|
|
594
603
|
' NEXO_CODE="$NEXO_HOME/core"\n'
|
|
595
604
|
' export NEXO_CODE\n'
|
package/src/cli.py
CHANGED
|
@@ -47,6 +47,7 @@ import time
|
|
|
47
47
|
from pathlib import Path
|
|
48
48
|
|
|
49
49
|
from runtime_home import export_resolved_nexo_home
|
|
50
|
+
from runtime_versioning import build_mcp_status, clear_restart_required_marker
|
|
50
51
|
|
|
51
52
|
NEXO_HOME = export_resolved_nexo_home()
|
|
52
53
|
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parent)))
|
|
@@ -120,6 +121,82 @@ def _fetch_latest_version(timeout_seconds: int = 2) -> str | None:
|
|
|
120
121
|
return latest
|
|
121
122
|
|
|
122
123
|
|
|
124
|
+
def _print_json_or_text(payload: dict, *, as_json: bool = False) -> int:
|
|
125
|
+
if as_json:
|
|
126
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
127
|
+
else:
|
|
128
|
+
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
129
|
+
return 0 if payload.get("ok", True) else 1
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _mcp_status(args) -> int:
|
|
133
|
+
return _print_json_or_text(
|
|
134
|
+
build_mcp_status(client=getattr(args, "client", "") or ""),
|
|
135
|
+
as_json=bool(getattr(args, "json", False)),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _mcp_clear_restart(args) -> int:
|
|
140
|
+
return _print_json_or_text(
|
|
141
|
+
clear_restart_required_marker(
|
|
142
|
+
client=getattr(args, "client", "") or "",
|
|
143
|
+
installed_version=getattr(args, "installed_version", "") or "",
|
|
144
|
+
process_version=getattr(args, "process_version", "") or "",
|
|
145
|
+
),
|
|
146
|
+
as_json=bool(getattr(args, "json", False)),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _continuity_snapshot_write(args) -> int:
|
|
151
|
+
from continuity import write_snapshot
|
|
152
|
+
|
|
153
|
+
result = write_snapshot(
|
|
154
|
+
conversation_id=args.conversation_id,
|
|
155
|
+
session_id=args.session_id or "",
|
|
156
|
+
external_session_id=args.external_session_id or "",
|
|
157
|
+
client=args.client or "",
|
|
158
|
+
event_type=args.event_type or "turn_end",
|
|
159
|
+
payload=args.payload or "",
|
|
160
|
+
trace_id=args.trace_id or "",
|
|
161
|
+
idempotency_key=args.idempotency_key or "",
|
|
162
|
+
)
|
|
163
|
+
return _print_json_or_text(result, as_json=bool(getattr(args, "json", False)))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _continuity_snapshot_read(args) -> int:
|
|
167
|
+
from continuity import read_snapshot
|
|
168
|
+
|
|
169
|
+
result = read_snapshot(
|
|
170
|
+
conversation_id=args.conversation_id or "",
|
|
171
|
+
session_id=args.session_id or "",
|
|
172
|
+
limit=args.limit or 20,
|
|
173
|
+
)
|
|
174
|
+
return _print_json_or_text(result, as_json=bool(getattr(args, "json", False)))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _continuity_resume_bundle(args) -> int:
|
|
178
|
+
from continuity import build_resume_bundle
|
|
179
|
+
|
|
180
|
+
result = build_resume_bundle(
|
|
181
|
+
conversation_id=args.conversation_id or "",
|
|
182
|
+
session_id=args.session_id or "",
|
|
183
|
+
external_session_id=args.external_session_id or "",
|
|
184
|
+
client=args.client or "",
|
|
185
|
+
token_budget=args.token_budget or 2000,
|
|
186
|
+
)
|
|
187
|
+
return _print_json_or_text(result, as_json=bool(getattr(args, "json", False)))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _continuity_audit(args) -> int:
|
|
191
|
+
from continuity import continuity_audit
|
|
192
|
+
|
|
193
|
+
result = continuity_audit(
|
|
194
|
+
conversation_id=args.conversation_id,
|
|
195
|
+
limit=args.limit or 50,
|
|
196
|
+
)
|
|
197
|
+
return _print_json_or_text(result, as_json=bool(getattr(args, "json", False)))
|
|
198
|
+
|
|
199
|
+
|
|
123
200
|
def _should_refresh_latest_version() -> bool:
|
|
124
201
|
"""Decide whether to hit the npm registry to refresh `latest` version.
|
|
125
202
|
|
|
@@ -3075,6 +3152,49 @@ def main():
|
|
|
3075
3152
|
dashboard_parser = sub.add_parser("dashboard", help="Web dashboard control")
|
|
3076
3153
|
dashboard_parser.add_argument("action", choices=["on", "off", "status"], help="Start, stop, or check dashboard")
|
|
3077
3154
|
|
|
3155
|
+
mcp_parser = sub.add_parser("mcp", help="MCP runtime status and restart state")
|
|
3156
|
+
mcp_sub = mcp_parser.add_subparsers(dest="mcp_command")
|
|
3157
|
+
mcp_status_p = mcp_sub.add_parser("status", help="Read the current runtime/MCP alignment state")
|
|
3158
|
+
mcp_status_p.add_argument("--client", default="", help="Optional client label such as claude_desktop or codex")
|
|
3159
|
+
mcp_status_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3160
|
+
mcp_clear_p = mcp_sub.add_parser("clear-restart", help="Acknowledge that a client/session reloaded the new runtime")
|
|
3161
|
+
mcp_clear_p.add_argument("--client", default="", help="Client label such as claude_desktop or codex")
|
|
3162
|
+
mcp_clear_p.add_argument("--installed-version", default="")
|
|
3163
|
+
mcp_clear_p.add_argument("--process-version", default="")
|
|
3164
|
+
mcp_clear_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3165
|
+
|
|
3166
|
+
continuity_parser = sub.add_parser("continuity", help="Continuity snapshots and resume bundles")
|
|
3167
|
+
continuity_sub = continuity_parser.add_subparsers(dest="continuity_command")
|
|
3168
|
+
cwrite_p = continuity_sub.add_parser("snapshot-write", help="Write a continuity snapshot")
|
|
3169
|
+
cwrite_p.add_argument("--conversation-id", required=True)
|
|
3170
|
+
cwrite_p.add_argument("--session-id", default="")
|
|
3171
|
+
cwrite_p.add_argument("--external-session-id", default="")
|
|
3172
|
+
cwrite_p.add_argument("--client", default="")
|
|
3173
|
+
cwrite_p.add_argument("--event-type", default="turn_end")
|
|
3174
|
+
cwrite_p.add_argument("--payload", default="")
|
|
3175
|
+
cwrite_p.add_argument("--trace-id", default="")
|
|
3176
|
+
cwrite_p.add_argument("--idempotency-key", default="")
|
|
3177
|
+
cwrite_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3178
|
+
|
|
3179
|
+
cread_p = continuity_sub.add_parser("snapshot-read", help="Read continuity snapshots")
|
|
3180
|
+
cread_p.add_argument("--conversation-id", default="")
|
|
3181
|
+
cread_p.add_argument("--session-id", default="")
|
|
3182
|
+
cread_p.add_argument("--limit", type=int, default=20)
|
|
3183
|
+
cread_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3184
|
+
|
|
3185
|
+
cbundle_p = continuity_sub.add_parser("resume-bundle", help="Build a continuity resume bundle")
|
|
3186
|
+
cbundle_p.add_argument("--conversation-id", default="")
|
|
3187
|
+
cbundle_p.add_argument("--session-id", default="")
|
|
3188
|
+
cbundle_p.add_argument("--external-session-id", default="")
|
|
3189
|
+
cbundle_p.add_argument("--client", default="")
|
|
3190
|
+
cbundle_p.add_argument("--token-budget", type=int, default=2000)
|
|
3191
|
+
cbundle_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3192
|
+
|
|
3193
|
+
caudit_p = continuity_sub.add_parser("audit", help="Read the continuity audit timeline")
|
|
3194
|
+
caudit_p.add_argument("--conversation-id", required=True)
|
|
3195
|
+
caudit_p.add_argument("--limit", type=int, default=50)
|
|
3196
|
+
caudit_p.add_argument("--json", action="store_true", help="JSON output")
|
|
3197
|
+
|
|
3078
3198
|
# -- desktop bridge (read-only, for NEXO Desktop and any external UI) --
|
|
3079
3199
|
# v7.4.0 — lifecycle event bridge (guardian-claude-desktop-plan).
|
|
3080
3200
|
lifecycle_parser = sub.add_parser("lifecycle", help="Conversation lifecycle event handler (v7.4 Desktop bridge)")
|
|
@@ -3268,6 +3388,24 @@ def main():
|
|
|
3268
3388
|
return _clients_sync(args)
|
|
3269
3389
|
clients_parser.print_help()
|
|
3270
3390
|
return 0
|
|
3391
|
+
elif args.command == "mcp":
|
|
3392
|
+
if args.mcp_command == "status":
|
|
3393
|
+
return _mcp_status(args)
|
|
3394
|
+
if args.mcp_command == "clear-restart":
|
|
3395
|
+
return _mcp_clear_restart(args)
|
|
3396
|
+
mcp_parser.print_help()
|
|
3397
|
+
return 0
|
|
3398
|
+
elif args.command == "continuity":
|
|
3399
|
+
if args.continuity_command == "snapshot-write":
|
|
3400
|
+
return _continuity_snapshot_write(args)
|
|
3401
|
+
elif args.continuity_command == "snapshot-read":
|
|
3402
|
+
return _continuity_snapshot_read(args)
|
|
3403
|
+
elif args.continuity_command == "resume-bundle":
|
|
3404
|
+
return _continuity_resume_bundle(args)
|
|
3405
|
+
elif args.continuity_command == "audit":
|
|
3406
|
+
return _continuity_audit(args)
|
|
3407
|
+
continuity_parser.print_help()
|
|
3408
|
+
return 0
|
|
3271
3409
|
elif args.command == "preferences":
|
|
3272
3410
|
return _preferences(args)
|
|
3273
3411
|
elif args.command == "doctor":
|