nexo-brain 7.31.12 → 7.31.13
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/package.json +1 -1
- package/src/hooks/post_tool_use.py +75 -1
- package/src/plugins/protocol.py +95 -0
- package/src/requirements.txt +12 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.13",
|
|
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.31.
|
|
21
|
+
Version `7.31.13` is the current packaged-runtime line. Patch release over v7.31.12 - the offline wheel bundle no longer hard-pins onnxruntime, so cross-platform/offline installs (including older Linux) resolve a compatible native wheel instead of failing. Version `7.31.11` was a patch release over v7.31.10 - MCP lifecycle robustness + guardrail precision.
|
|
22
22
|
|
|
23
23
|
Previously in `7.31.9`: patch release over v7.31.8 - UI release closeout now has to prove the original reported symptom was reopened with observable evidence before claiming the release is ready.
|
|
24
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.13",
|
|
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",
|
|
@@ -415,6 +415,73 @@ def check_production_change_log_closeout(payload: dict, sid: str) -> str | None:
|
|
|
415
415
|
return append_operator_language_contract(message)
|
|
416
416
|
|
|
417
417
|
|
|
418
|
+
_SHARED_MUTATION_TOOLS = {
|
|
419
|
+
"Edit",
|
|
420
|
+
"Write",
|
|
421
|
+
"MultiEdit",
|
|
422
|
+
"NotebookEdit",
|
|
423
|
+
"apply_patch",
|
|
424
|
+
"functions.apply_patch",
|
|
425
|
+
}
|
|
426
|
+
_SHARED_PATH_RE = re.compile(
|
|
427
|
+
r"("
|
|
428
|
+
r"/Users/[^ \n\r\t'\"]+/Documents/_PhpstormProjects/|"
|
|
429
|
+
r"/Users/[^ \n\r\t'\"]+/.nexo/core/|"
|
|
430
|
+
r"/home/nexodesk/|"
|
|
431
|
+
r"/var/www/|"
|
|
432
|
+
r"/public_html/|"
|
|
433
|
+
r"/httpdocs/"
|
|
434
|
+
r")",
|
|
435
|
+
re.IGNORECASE,
|
|
436
|
+
)
|
|
437
|
+
_SCOPE_REQUIRED_MARKERS = {
|
|
438
|
+
"conversation": re.compile(r"\b(conversation|conversaci[oó]n|hilo|thread|email|ticket|mensaje|message|n/a)\b", re.IGNORECASE),
|
|
439
|
+
"tenant": re.compile(r"\b(tenant|tienda|shop|cuenta|account|cliente|client|n/a)\b", re.IGNORECASE),
|
|
440
|
+
"language": re.compile(r"\b(idioma|language|lang|locale|es|en|de|fr|pt|it|ca|n/a)\b", re.IGNORECASE),
|
|
441
|
+
"environment": re.compile(r"\b(entorno|environment|local|runtime|producto|producci[oó]n|production|prod|staging)\b", re.IGNORECASE),
|
|
442
|
+
"surface": re.compile(r"\b(superficie|surface|api|ui|dominio|domain|web|public)\b", re.IGNORECASE),
|
|
443
|
+
"deploy": re.compile(r"\b(deploy|despliegue|publicado|published|release|rama|branch|n/a)\b", re.IGNORECASE),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _payload_scope_text(payload: dict) -> str:
|
|
448
|
+
try:
|
|
449
|
+
return json.dumps(_tool_input(payload), ensure_ascii=False)
|
|
450
|
+
except Exception:
|
|
451
|
+
return str(_tool_input(payload) or "")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _is_shared_mutation_payload(payload: dict) -> bool:
|
|
455
|
+
tool_name = _tool_name(payload)
|
|
456
|
+
cmd = _extract_command(payload)
|
|
457
|
+
input_text = _payload_scope_text(payload)
|
|
458
|
+
combined = "\n".join(part for part in (tool_name, cmd, input_text) if part)
|
|
459
|
+
if tool_name in _SHARED_MUTATION_TOOLS and _SHARED_PATH_RE.search(combined):
|
|
460
|
+
return True
|
|
461
|
+
if cmd and (_is_production_mutation_command(cmd) or _SHARED_PATH_RE.search(cmd)):
|
|
462
|
+
return True
|
|
463
|
+
return False
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def check_shared_scope_closeout(payload: dict) -> str | None:
|
|
467
|
+
if not _is_shared_mutation_payload(payload):
|
|
468
|
+
return None
|
|
469
|
+
scope_text = _payload_scope_text(payload)
|
|
470
|
+
missing = [
|
|
471
|
+
label
|
|
472
|
+
for label, pattern in _SCOPE_REQUIRED_MARKERS.items()
|
|
473
|
+
if not pattern.search(scope_text)
|
|
474
|
+
]
|
|
475
|
+
if not missing:
|
|
476
|
+
return None
|
|
477
|
+
message = (
|
|
478
|
+
"Antes de seguir con este cambio compartido, deja fijado el alcance operativo: "
|
|
479
|
+
"conversación afectada, tenant/tienda, idiomas, entorno, superficie pública y estado de deploy. "
|
|
480
|
+
"Si algún campo no aplica, márcalo como N/A y continúa con evidencia."
|
|
481
|
+
)
|
|
482
|
+
return append_operator_language_contract(message)
|
|
483
|
+
|
|
484
|
+
|
|
418
485
|
def _run_auto_capture(payload: dict) -> int:
|
|
419
486
|
"""Pipe the tool result into auto_capture for post-output classification."""
|
|
420
487
|
text = _extract_tool_text(payload)
|
|
@@ -484,13 +551,20 @@ def main() -> int:
|
|
|
484
551
|
sid = _resolve_sid_from_payload(payload)
|
|
485
552
|
reminder = check_inbox_and_emit_reminder(sid)
|
|
486
553
|
change_log_message = check_production_change_log_closeout(payload, sid)
|
|
554
|
+
shared_scope_message = check_shared_scope_closeout(payload)
|
|
487
555
|
g1_message: str | None = None
|
|
488
556
|
try:
|
|
489
557
|
from g1_enforcer import check_response_contract_gate # type: ignore
|
|
490
558
|
g1_message = check_response_contract_gate(sid)
|
|
491
559
|
except Exception:
|
|
492
560
|
g1_message = None
|
|
493
|
-
combined = _combine_system_messages(
|
|
561
|
+
combined = _combine_system_messages(
|
|
562
|
+
protocol_message,
|
|
563
|
+
reminder,
|
|
564
|
+
change_log_message,
|
|
565
|
+
shared_scope_message,
|
|
566
|
+
g1_message,
|
|
567
|
+
)
|
|
494
568
|
if combined:
|
|
495
569
|
print(json.dumps({"systemMessage": combined}))
|
|
496
570
|
except Exception:
|
package/src/plugins/protocol.py
CHANGED
|
@@ -1307,6 +1307,58 @@ PUBLIC_RELEASE_WORK_RE = re.compile(
|
|
|
1307
1307
|
re.IGNORECASE,
|
|
1308
1308
|
)
|
|
1309
1309
|
HIGH_STAKES_WORK_TYPES = {"release", "deploy", "deployment", "publish", "publicar", "desplegar"}
|
|
1310
|
+
VISIBLE_RELEASE_SURFACE_PATTERNS = {
|
|
1311
|
+
"api": (
|
|
1312
|
+
re.compile(r"\bapi\b\s*[:=-]", re.IGNORECASE),
|
|
1313
|
+
re.compile(r"\bendpoint(?:s)?\b\s*[:=-]", re.IGNORECASE),
|
|
1314
|
+
re.compile(r"\bruta(?:s)?\b\s*[:=-]", re.IGNORECASE),
|
|
1315
|
+
),
|
|
1316
|
+
"ui": (
|
|
1317
|
+
re.compile(r"\bui\b\s*[:=-]", re.IGNORECASE),
|
|
1318
|
+
re.compile(r"\bux\b\s*[:=-]", re.IGNORECASE),
|
|
1319
|
+
re.compile(r"\binterfaz\b\s*[:=-]", re.IGNORECASE),
|
|
1320
|
+
re.compile(r"\bnavegador\b\s*[:=-]", re.IGNORECASE),
|
|
1321
|
+
re.compile(r"\bbrowser\b\s*[:=-]", re.IGNORECASE),
|
|
1322
|
+
),
|
|
1323
|
+
"dominio_publico": (
|
|
1324
|
+
re.compile(r"\bdominio\s+p[uú]blico\b\s*[:=-]", re.IGNORECASE),
|
|
1325
|
+
re.compile(r"\bpublic\s+domain\b\s*[:=-]", re.IGNORECASE),
|
|
1326
|
+
re.compile(r"\burl\s+p[uú]blica\b\s*[:=-]", re.IGNORECASE),
|
|
1327
|
+
re.compile(r"\bpublic\s+url\b\s*[:=-]", re.IGNORECASE),
|
|
1328
|
+
re.compile(r"https?://[^\s]+", re.IGNORECASE),
|
|
1329
|
+
),
|
|
1330
|
+
"rama_publicacion": (
|
|
1331
|
+
re.compile(r"\brama\b\s*[:=-]", re.IGNORECASE),
|
|
1332
|
+
re.compile(r"\bbranch\b\s*[:=-]", re.IGNORECASE),
|
|
1333
|
+
re.compile(r"\borigin/(?:main|master|stable|release|gh-pages)\b", re.IGNORECASE),
|
|
1334
|
+
re.compile(r"\bgh-pages\b", re.IGNORECASE),
|
|
1335
|
+
),
|
|
1336
|
+
"artefactos": (
|
|
1337
|
+
re.compile(r"\bartefacto(?:s)?\b\s*[:=-]", re.IGNORECASE),
|
|
1338
|
+
re.compile(r"\bartifact(?:s)?\b\s*[:=-]", re.IGNORECASE),
|
|
1339
|
+
re.compile(r"\b(?:dmg|exe|zip|image|imagen|cloud\s+build|github\s+release)\b", re.IGNORECASE),
|
|
1340
|
+
),
|
|
1341
|
+
"manifiestos": (
|
|
1342
|
+
re.compile(r"\bmanifiesto(?:s)?\b\s*[:=-]", re.IGNORECASE),
|
|
1343
|
+
re.compile(r"\bmanifest(?:s)?\b\s*[:=-]", re.IGNORECASE),
|
|
1344
|
+
re.compile(r"\b(?:manifest\.json|update\.json|package\.json|composer\.json)\b", re.IGNORECASE),
|
|
1345
|
+
),
|
|
1346
|
+
"prueba_viva": (
|
|
1347
|
+
re.compile(r"\bprueba\s+viva\b\s*[:=-]", re.IGNORECASE),
|
|
1348
|
+
re.compile(r"\blive\s+test\b\s*[:=-]", re.IGNORECASE),
|
|
1349
|
+
re.compile(r"\bsmoke\b\s*[:=-]", re.IGNORECASE),
|
|
1350
|
+
re.compile(r"\b(?:curl|playwright|logs?\s+vivos?|gcloud\s+logs|http\s*200|200\s+ok)\b", re.IGNORECASE),
|
|
1351
|
+
),
|
|
1352
|
+
}
|
|
1353
|
+
VISIBLE_RELEASE_SURFACE_LABELS = {
|
|
1354
|
+
"api": "API",
|
|
1355
|
+
"ui": "UI",
|
|
1356
|
+
"dominio_publico": "dominio público",
|
|
1357
|
+
"rama_publicacion": "rama de publicación",
|
|
1358
|
+
"artefactos": "artefactos",
|
|
1359
|
+
"manifiestos": "manifiestos",
|
|
1360
|
+
"prueba_viva": "prueba viva",
|
|
1361
|
+
}
|
|
1310
1362
|
|
|
1311
1363
|
|
|
1312
1364
|
def _normalize_artifact_hash(value: str) -> str:
|
|
@@ -1344,6 +1396,15 @@ def _has_public_release_evidence(evidence: str) -> bool:
|
|
|
1344
1396
|
return bool(PUBLIC_RELEASE_EVIDENCE_RE.search(evidence or ""))
|
|
1345
1397
|
|
|
1346
1398
|
|
|
1399
|
+
def _missing_visible_release_surfaces(evidence: str) -> list[str]:
|
|
1400
|
+
text = evidence or ""
|
|
1401
|
+
missing: list[str] = []
|
|
1402
|
+
for key, patterns in VISIBLE_RELEASE_SURFACE_PATTERNS.items():
|
|
1403
|
+
if not any(pattern.search(text) for pattern in patterns):
|
|
1404
|
+
missing.append(VISIBLE_RELEASE_SURFACE_LABELS[key])
|
|
1405
|
+
return missing
|
|
1406
|
+
|
|
1407
|
+
|
|
1347
1408
|
def _active_followup_snapshot(limit: int = 5) -> list[dict]:
|
|
1348
1409
|
try:
|
|
1349
1410
|
followups = get_followups("active")
|
|
@@ -2476,6 +2537,40 @@ def handle_task_close(
|
|
|
2476
2537
|
indent=2,
|
|
2477
2538
|
)
|
|
2478
2539
|
|
|
2540
|
+
if clean_outcome == "done" and _is_high_stakes_public_work(task, work_type, stakes, closure_text):
|
|
2541
|
+
missing_surfaces = _missing_visible_release_surfaces(live_surface_evidence)
|
|
2542
|
+
if missing_surfaces:
|
|
2543
|
+
debt = _ensure_open_debt(
|
|
2544
|
+
task["session_id"],
|
|
2545
|
+
task_id,
|
|
2546
|
+
"visible_release_surface_matrix_incomplete",
|
|
2547
|
+
severity="error",
|
|
2548
|
+
evidence=(
|
|
2549
|
+
"Visible release/deploy/fix close lacked the full evidence matrix. "
|
|
2550
|
+
f"Missing surfaces: {', '.join(missing_surfaces)}. "
|
|
2551
|
+
f"Evidence provided: {live_surface_evidence[:240]!r}"
|
|
2552
|
+
),
|
|
2553
|
+
debts=debts_created,
|
|
2554
|
+
)
|
|
2555
|
+
return json.dumps(
|
|
2556
|
+
{
|
|
2557
|
+
"ok": False,
|
|
2558
|
+
"error": "Cannot close visible release/deploy/fix as done without the full surface evidence matrix.",
|
|
2559
|
+
"hint": (
|
|
2560
|
+
"Enumerate API, UI, public domain, publication branch, artifacts, manifests, "
|
|
2561
|
+
"and live test evidence. Use N/A only for a surface that truly does not exist."
|
|
2562
|
+
),
|
|
2563
|
+
"task_id": task_id,
|
|
2564
|
+
"blocked_by": "visible_release_surface_matrix",
|
|
2565
|
+
"debt_id": debt.get("id"),
|
|
2566
|
+
"debt_type": "visible_release_surface_matrix_incomplete",
|
|
2567
|
+
"missing_surfaces": missing_surfaces,
|
|
2568
|
+
"response_mode": "verify",
|
|
2569
|
+
},
|
|
2570
|
+
ensure_ascii=False,
|
|
2571
|
+
indent=2,
|
|
2572
|
+
)
|
|
2573
|
+
|
|
2479
2574
|
if task.get("guard_has_blocking") and not files_changed_list:
|
|
2480
2575
|
open_task_debts = list_protocol_debts(status="open", task_id=task_id, limit=200)
|
|
2481
2576
|
has_guard_touch_violation = any(
|
package/src/requirements.txt
CHANGED
|
@@ -17,15 +17,19 @@ anthropic>=0.80.0
|
|
|
17
17
|
openai>=2.20.0
|
|
18
18
|
|
|
19
19
|
# Embedding model (optional but recommended for cognitive features).
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
20
|
+
# fastembed is hard-pinned (==) so the offline wheel bundle is reproducible; it is
|
|
21
|
+
# pure-python (py3-none-any) so the pin holds on every platform. >=0.8.0 was the
|
|
22
|
+
# floor (older releases need Python <3.12 and pip iterates each obsolete version for
|
|
23
|
+
# ~10 min on Ubuntu 24.04 before finding a compatible one, verified during Win11
|
|
24
|
+
# clean install bootstrap); 0.8.0 is the bundled version.
|
|
25
|
+
#
|
|
26
|
+
# onnxruntime (fastembed's native transitive dep) is deliberately NOT hard-pinned:
|
|
27
|
+
# its wheels are platform/glibc-specific — e.g. 1.26 only ships for manylinux_2_28,
|
|
28
|
+
# while the offline bundle also targets manylinux_2_17/2014 (which cap at 1.19.2).
|
|
29
|
+
# A `==` pin can't be satisfied for every target and breaks BOTH the cross-platform
|
|
30
|
+
# wheel bundle and installs on older Linux. Let fastembed/pip resolve the best
|
|
31
|
+
# onnxruntime wheel per platform.
|
|
27
32
|
fastembed==0.8.0
|
|
28
|
-
onnxruntime==1.26.0
|
|
29
33
|
|
|
30
34
|
# Local Context Layer — document parsers (REQUIRED for the local memory index).
|
|
31
35
|
# extractors.py imports these lazily; without them a clean bundle silently indexes
|