nexo-brain 7.9.9 → 7.9.10
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 +1 -1
- package/bin/nexo-brain.js +73 -4
- package/package.json +1 -1
- package/src/doctor/planes.py +9 -5
- package/src/plugins/doctor.py +0 -15
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.10",
|
|
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,7 @@
|
|
|
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.10` is the current packaged-runtime line. Hotfix release over `7.9.9`: `nexo_doctor` now defaults blank calls to `runtime_personal` instead of failing on missing `plane`, and non-interactive installer/update paths preserve existing identity defaults from runtime profile/calibration metadata instead of blindly rewriting operators to generic onboarding defaults. Desktop remains at v0.28.11 because the extra fixes are Brain-only.
|
|
22
22
|
|
|
23
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.
|
|
24
24
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -252,6 +252,24 @@ function readRuntimeCalibration(nexoHome = NEXO_HOME) {
|
|
|
252
252
|
return { path: null, payload: null };
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
function profilePathCandidates(nexoHome = NEXO_HOME) {
|
|
256
|
+
return [
|
|
257
|
+
path.join(nexoHome, "personal", "brain", "profile.json"),
|
|
258
|
+
path.join(nexoHome, "brain", "profile.json"),
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readRuntimeProfile(nexoHome = NEXO_HOME) {
|
|
263
|
+
for (const filePath of profilePathCandidates(nexoHome)) {
|
|
264
|
+
if (!fs.existsSync(filePath)) continue;
|
|
265
|
+
const payload = readJsonFile(filePath);
|
|
266
|
+
if (payload && typeof payload === "object") {
|
|
267
|
+
return { path: filePath, payload };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { path: null, payload: null };
|
|
271
|
+
}
|
|
272
|
+
|
|
255
273
|
function nonEmptyString(value) {
|
|
256
274
|
return typeof value === "string" && value.trim().length > 0;
|
|
257
275
|
}
|
|
@@ -288,6 +306,55 @@ function hasPartialPlaceholderCalibration(calibration) {
|
|
|
288
306
|
return isPlaceholderUserName(name) || !nonEmptyString(language);
|
|
289
307
|
}
|
|
290
308
|
|
|
309
|
+
function firstMeaningfulString(...values) {
|
|
310
|
+
for (const value of values) {
|
|
311
|
+
if (typeof value !== "string") continue;
|
|
312
|
+
const clean = value.trim();
|
|
313
|
+
if (clean) return clean;
|
|
314
|
+
}
|
|
315
|
+
return "";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeLanguageCode(value) {
|
|
319
|
+
const clean = String(value || "").trim().toLowerCase().replace("_", "-");
|
|
320
|
+
if (!clean) return "";
|
|
321
|
+
return clean.split("-")[0];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function resolveExistingIdentityDefaults(nexoHome = NEXO_HOME) {
|
|
325
|
+
const calibration = readRuntimeCalibration(nexoHome).payload || {};
|
|
326
|
+
const user = calibration.user && typeof calibration.user === "object" ? calibration.user : {};
|
|
327
|
+
const profile = readRuntimeProfile(nexoHome).payload || {};
|
|
328
|
+
const version = readJsonFile(path.join(nexoHome, "version.json")) || {};
|
|
329
|
+
|
|
330
|
+
const userName = firstMeaningfulString(
|
|
331
|
+
user.name,
|
|
332
|
+
calibration.user_name,
|
|
333
|
+
profile.user_name,
|
|
334
|
+
version.user_name,
|
|
335
|
+
);
|
|
336
|
+
const language = normalizeLanguageCode(firstMeaningfulString(
|
|
337
|
+
user.language,
|
|
338
|
+
calibration.language,
|
|
339
|
+
profile.language,
|
|
340
|
+
version.language,
|
|
341
|
+
));
|
|
342
|
+
const operatorName = firstMeaningfulString(
|
|
343
|
+
user.assistant_name,
|
|
344
|
+
calibration.assistant_name,
|
|
345
|
+
calibration.operator_name,
|
|
346
|
+
profile.assistant_name,
|
|
347
|
+
profile.operator_name,
|
|
348
|
+
version.operator_name,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
userName: !isPlaceholderUserName(userName) ? userName : "",
|
|
353
|
+
language,
|
|
354
|
+
operatorName: !isReservedAssistantName(operatorName) ? operatorName : "",
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
291
358
|
function ensureOnboardingCompletionMarker(nexoHome = NEXO_HOME) {
|
|
292
359
|
const record = readRuntimeCalibration(nexoHome);
|
|
293
360
|
if (!record.path || !isOnboardingComplete(record.payload)) {
|
|
@@ -2966,9 +3033,11 @@ async function runSetup() {
|
|
|
2966
3033
|
},
|
|
2967
3034
|
};
|
|
2968
3035
|
|
|
3036
|
+
const existingIdentity = resolveExistingIdentityDefaults(NEXO_HOME);
|
|
3037
|
+
|
|
2969
3038
|
// Detect language from input or use default
|
|
2970
|
-
let lang = "en";
|
|
2971
|
-
let t = i18n.en;
|
|
3039
|
+
let lang = existingIdentity.language || "en";
|
|
3040
|
+
let t = i18n[lang] || i18n.en;
|
|
2972
3041
|
if (!useDefaults) {
|
|
2973
3042
|
const langInput = await ask(" What's your preferred language? / ¿En qué idioma prefieres hablar?\n > ");
|
|
2974
3043
|
const langLower = langInput.trim().toLowerCase();
|
|
@@ -3009,7 +3078,7 @@ async function runSetup() {
|
|
|
3009
3078
|
// Step 2: User's name (P2) — v6.0.0 empty input falls through to "Usuario"
|
|
3010
3079
|
// instead of keeping an empty string. The calibration file always ships
|
|
3011
3080
|
// with a concrete user.name so downstream tooling does not need guards.
|
|
3012
|
-
let userName = "Usuario";
|
|
3081
|
+
let userName = existingIdentity.userName || "Usuario";
|
|
3013
3082
|
if (!useDefaults) {
|
|
3014
3083
|
const nameInput = await ask(t.askUserName);
|
|
3015
3084
|
const trimmedName = nameInput.trim();
|
|
@@ -3021,7 +3090,7 @@ async function runSetup() {
|
|
|
3021
3090
|
}
|
|
3022
3091
|
|
|
3023
3092
|
// Step 3: Agent name (P3)
|
|
3024
|
-
let operatorName = DEFAULT_ASSISTANT_NAME;
|
|
3093
|
+
let operatorName = existingIdentity.operatorName || DEFAULT_ASSISTANT_NAME;
|
|
3025
3094
|
if (!useDefaults) {
|
|
3026
3095
|
while (true) {
|
|
3027
3096
|
const name = await ask(t.askAgentName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.10",
|
|
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/doctor/planes.py
CHANGED
|
@@ -28,6 +28,7 @@ VALID_DIAGNOSTIC_PLANES = {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
DOCTOR_COMPATIBLE_PLANES = {"runtime_personal", "installation_live", "database_real"}
|
|
31
|
+
DEFAULT_DOCTOR_PLANE = "runtime_personal"
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def normalize_diagnostic_plane(plane: str = "") -> str:
|
|
@@ -40,15 +41,19 @@ def diagnostic_plane_choices() -> list[str]:
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
def diagnostic_plane_preflight(plane: str = "") -> tuple[str, DoctorCheck | None]:
|
|
43
|
-
|
|
44
|
+
raw_plane = str(plane or "").strip()
|
|
45
|
+
if not raw_plane:
|
|
46
|
+
return DEFAULT_DOCTOR_PLANE, None
|
|
47
|
+
|
|
48
|
+
clean_plane = normalize_diagnostic_plane(raw_plane)
|
|
44
49
|
if not clean_plane:
|
|
45
50
|
options = ", ".join(diagnostic_plane_choices())
|
|
46
51
|
return "", DoctorCheck(
|
|
47
|
-
id="orchestrator.
|
|
52
|
+
id="orchestrator.diagnostic_plane_invalid",
|
|
48
53
|
tier="orchestrator",
|
|
49
54
|
status="critical",
|
|
50
55
|
severity="error",
|
|
51
|
-
summary="
|
|
56
|
+
summary=f"Plano diagnóstico desconocido: {raw_plane}",
|
|
52
57
|
evidence=[
|
|
53
58
|
f"planes válidos: {options}",
|
|
54
59
|
"Usa `runtime_personal` para ~/.nexo y hábitos del runtime; `installation_live` para hooks/clientes/instalación; `database_real` para filas y schema reales.",
|
|
@@ -58,8 +63,7 @@ def diagnostic_plane_preflight(plane: str = "") -> tuple[str, DoctorCheck | None
|
|
|
58
63
|
"Si el problema pertenece a producto público o al co-operador, usa el surface correcto en vez de NEXO Doctor.",
|
|
59
64
|
],
|
|
60
65
|
escalation_prompt=(
|
|
61
|
-
"
|
|
62
|
-
"explícitamente si el problema está en producto público, runtime personal, instalación viva, BD real o co-operador."
|
|
66
|
+
"El plano elegido no existe. Repite el diagnóstico con un plano válido para evitar mezclar runtime, instalación, BD real o surfaces ajenas al doctor."
|
|
63
67
|
),
|
|
64
68
|
)
|
|
65
69
|
|
package/src/plugins/doctor.py
CHANGED
|
@@ -22,26 +22,11 @@ def handle_doctor(tier: str = "boot", fix: bool = False, output: str = "text", p
|
|
|
22
22
|
"""
|
|
23
23
|
from doctor.orchestrator import run_doctor
|
|
24
24
|
from doctor.formatters import format_report
|
|
25
|
-
from doctor.planes import diagnostic_plane_choices
|
|
26
25
|
|
|
27
26
|
if tier not in ("boot", "runtime", "deep", "all"):
|
|
28
27
|
return f"Invalid tier '{tier}'. Use: boot, runtime, deep, all"
|
|
29
28
|
if output not in ("text", "json"):
|
|
30
29
|
return f"Invalid output '{output}'. Use: text, json"
|
|
31
|
-
if not (plane or "").strip():
|
|
32
|
-
valid_planes = diagnostic_plane_choices()
|
|
33
|
-
if output == "json":
|
|
34
|
-
return (
|
|
35
|
-
"{"
|
|
36
|
-
f"\"ok\": false, \"error\": \"Missing required argument: plane\", "
|
|
37
|
-
"\"missing_argument\": \"plane\", "
|
|
38
|
-
f"\"valid_planes\": {valid_planes!r}"
|
|
39
|
-
"}"
|
|
40
|
-
).replace("'", '"')
|
|
41
|
-
return (
|
|
42
|
-
"Missing required argument: plane. "
|
|
43
|
-
f"Use one of: {', '.join(valid_planes)}."
|
|
44
|
-
)
|
|
45
30
|
|
|
46
31
|
report = run_doctor(tier=tier, fix=fix, plane=plane)
|
|
47
32
|
return format_report(report, fmt=output)
|