nexo-brain 1.7.0 → 2.0.0
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 +25 -24
- package/bin/nexo-brain.js +680 -381
- package/package.json +4 -1
- package/scripts/migrate-to-unified.sh +813 -0
- package/scripts/migrate-v1.7-to-v1.8.py +214 -0
- package/scripts/pre-commit-check.sh +1 -1
- package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-314.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-310.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-310.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/server.cpython-310.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions.py +1 -1
- package/src/auto_update.py +634 -0
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core.py +7 -3
- package/src/cognitive/_decay.py +1 -1
- package/src/cognitive/_search.py +1 -0
- package/src/cognitive/_trust.py +3 -3
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app.py +8 -2
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core.py +5 -1
- package/src/db/_episodic.py +1 -3
- package/src/db/_reminders.py +36 -1
- package/src/db/_schema.py +31 -0
- package/src/evolution_cycle.py +33 -11
- package/src/hooks/__pycache__/auto_capture.cpython-310.pyc +0 -0
- package/src/hooks/auto_capture.py +1 -1
- package/src/hooks/capture-tool-logs.sh +76 -0
- package/src/hooks/inbox-hook.sh +2 -1
- package/src/hooks/post-compact.sh +2 -1
- package/src/hooks/pre-compact.sh +104 -2
- package/src/hooks/session-start.sh +6 -2
- package/src/hooks/session-stop.sh +2 -1
- package/src/kg_populate.py +4 -1
- package/src/migrate_embeddings.py +4 -1
- package/src/plugin_loader.py +100 -34
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/agents.py +2 -2
- package/src/plugins/backup.py +5 -4
- package/src/plugins/core_rules.py +5 -1
- package/src/plugins/episodic_memory.py +14 -5
- package/src/plugins/evolution.py +6 -2
- package/src/plugins/guard.py +20 -11
- package/src/plugins/update.py +238 -0
- package/src/requirements.txt +12 -0
- package/src/rules/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/check-context.py +9 -1
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/apply_findings.py +3 -3
- package/src/scripts/nexo-auto-update.py +4 -211
- package/src/scripts/nexo-backup.sh +5 -13
- package/src/scripts/nexo-brain-activation.sh +26 -26
- package/src/scripts/nexo-catchup.py +36 -22
- package/src/scripts/nexo-cognitive-decay.py +7 -3
- package/src/scripts/nexo-daily-self-audit.py +30 -10
- package/src/scripts/nexo-evolution-run.py +35 -10
- package/src/scripts/nexo-followup-hygiene.py +2 -2
- package/src/scripts/nexo-github-monitor.py +11 -4
- package/src/scripts/nexo-immune.py +17 -2
- package/src/scripts/nexo-inbox-hook.sh +2 -1
- package/src/scripts/nexo-install.py +4 -225
- package/src/scripts/nexo-learning-housekeep.py +7 -3
- package/src/scripts/nexo-learning-validator.py +10 -2
- package/src/scripts/nexo-migrate.py +9 -3
- package/src/scripts/nexo-postmortem-consolidator.py +22 -4
- package/src/scripts/nexo-pre-commit.py +3 -1
- package/src/scripts/nexo-prevent-sleep.sh +29 -0
- package/src/scripts/nexo-proactive-dashboard.py +4 -4
- package/src/scripts/nexo-runtime-preflight.py +59 -55
- package/src/scripts/nexo-send-email.py +1 -1
- package/src/scripts/nexo-send-reply.py +3 -1
- package/src/scripts/nexo-sleep.py +19 -5
- package/src/scripts/nexo-snapshot-restore.sh +2 -1
- package/src/scripts/nexo-synthesis.py +17 -2
- package/src/scripts/nexo-tcc-approve.sh +79 -0
- package/src/scripts/nexo-update.sh +161 -0
- package/src/scripts/nexo-watchdog-smoke.py +18 -13
- package/src/scripts/nexo-watchdog.sh +22 -13
- package/src/server.py +77 -28
- package/src/storage_router.py +6 -2
- package/src/tools_learnings.py +6 -6
- package/src/tools_reminders_crud.py +10 -8
- package/src/tools_sessions.py +9 -4
- package/templates/CLAUDE.md.template +14 -80
- package/templates/launchagents/README.md +7 -7
- package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
- package/templates/launchagents/com.nexo.catchup.plist +4 -0
- package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
- package/templates/launchagents/com.nexo.dashboard.plist +5 -1
- package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
- package/templates/launchagents/com.nexo.evolution.plist +4 -0
- package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
- package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
- package/templates/launchagents/com.nexo.immune.plist +4 -0
- package/templates/launchagents/com.nexo.postmortem.plist +4 -0
- package/templates/launchagents/com.nexo.self-audit.plist +4 -0
- package/templates/launchagents/com.nexo.synthesis.plist +4 -0
- package/templates/launchagents/com.nexo.watchdog.plist +4 -0
- package/templates/openclaw.json +1 -1
- package/tests/__pycache__/__init__.cpython-310.pyc +0 -0
- package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/conftest.py +2 -2
- package/tests/test_cognitive.py +7 -6
- package/tests/test_migrations.py +26 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Update plugin — pull latest code, backup DBs, run migrations, verify."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import sqlite3
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Repo root: go up from src/plugins/ -> src/ -> repo/
|
|
12
|
+
_THIS_DIR = Path(__file__).resolve().parent
|
|
13
|
+
REPO_DIR = _THIS_DIR.parent.parent
|
|
14
|
+
PACKAGE_JSON = REPO_DIR / "package.json"
|
|
15
|
+
SRC_DIR = REPO_DIR / "src"
|
|
16
|
+
|
|
17
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
18
|
+
DATA_DIR = NEXO_HOME / "data"
|
|
19
|
+
BACKUP_BASE = NEXO_HOME / "backups"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _read_version() -> str:
|
|
23
|
+
"""Read version from package.json."""
|
|
24
|
+
try:
|
|
25
|
+
return json.loads(PACKAGE_JSON.read_text()).get("version", "unknown")
|
|
26
|
+
except Exception:
|
|
27
|
+
return "unknown"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _git(*args, cwd=None) -> tuple[int, str, str]:
|
|
31
|
+
"""Run a git command and return (returncode, stdout, stderr)."""
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["git"] + list(args),
|
|
34
|
+
cwd=cwd or str(REPO_DIR),
|
|
35
|
+
capture_output=True,
|
|
36
|
+
text=True,
|
|
37
|
+
timeout=60,
|
|
38
|
+
)
|
|
39
|
+
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _check_dirty() -> str | None:
|
|
43
|
+
"""Return error message if src/ has uncommitted changes, else None."""
|
|
44
|
+
rc, out, _ = _git("status", "--porcelain", "--", "src/")
|
|
45
|
+
if rc != 0:
|
|
46
|
+
return "Failed to check git status."
|
|
47
|
+
if out:
|
|
48
|
+
return f"Uncommitted changes in src/:\n{out}\nCommit or stash before updating."
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _backup_databases() -> tuple[str, str | None]:
|
|
53
|
+
"""Backup all .db files from NEXO_HOME/data/. Returns (backup_dir, error)."""
|
|
54
|
+
timestamp = time.strftime("%Y-%m-%d-%H%M")
|
|
55
|
+
backup_dir = BACKUP_BASE / f"pre-update-{timestamp}"
|
|
56
|
+
|
|
57
|
+
db_files = list(DATA_DIR.glob("*.db")) if DATA_DIR.is_dir() else []
|
|
58
|
+
# Also check NEXO_HOME root for legacy db location
|
|
59
|
+
db_files += [f for f in NEXO_HOME.glob("*.db") if f.is_file()]
|
|
60
|
+
# And check src/ dir for nexo.db (dev mode)
|
|
61
|
+
src_db = SRC_DIR / "nexo.db"
|
|
62
|
+
if src_db.is_file() and src_db not in db_files:
|
|
63
|
+
db_files.append(src_db)
|
|
64
|
+
|
|
65
|
+
if not db_files:
|
|
66
|
+
return str(backup_dir), None # No DBs to backup, not an error
|
|
67
|
+
|
|
68
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
for db_file in db_files:
|
|
71
|
+
dest = backup_dir / db_file.name
|
|
72
|
+
try:
|
|
73
|
+
src_conn = sqlite3.connect(str(db_file))
|
|
74
|
+
dst_conn = sqlite3.connect(str(dest))
|
|
75
|
+
src_conn.backup(dst_conn)
|
|
76
|
+
dst_conn.close()
|
|
77
|
+
src_conn.close()
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return str(backup_dir), f"Failed to backup {db_file.name}: {e}"
|
|
80
|
+
|
|
81
|
+
return str(backup_dir), None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _restore_databases(backup_dir: str):
|
|
85
|
+
"""Restore .db files from a backup directory."""
|
|
86
|
+
bdir = Path(backup_dir)
|
|
87
|
+
if not bdir.is_dir():
|
|
88
|
+
return
|
|
89
|
+
for db_backup in bdir.glob("*.db"):
|
|
90
|
+
# Try to find original location
|
|
91
|
+
for candidate in [DATA_DIR / db_backup.name, NEXO_HOME / db_backup.name, SRC_DIR / db_backup.name]:
|
|
92
|
+
if candidate.is_file():
|
|
93
|
+
try:
|
|
94
|
+
src_conn = sqlite3.connect(str(db_backup))
|
|
95
|
+
dst_conn = sqlite3.connect(str(candidate))
|
|
96
|
+
src_conn.backup(dst_conn)
|
|
97
|
+
dst_conn.close()
|
|
98
|
+
src_conn.close()
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _run_migrations() -> str | None:
|
|
105
|
+
"""Run init_db() to apply pending migrations. Returns error or None."""
|
|
106
|
+
try:
|
|
107
|
+
result = subprocess.run(
|
|
108
|
+
[sys.executable, "-c", "import db; db.init_db()"],
|
|
109
|
+
cwd=str(SRC_DIR),
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
timeout=30,
|
|
113
|
+
)
|
|
114
|
+
if result.returncode != 0:
|
|
115
|
+
return f"Migration failed: {result.stderr or result.stdout}"
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return f"Migration error: {e}"
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _verify_import() -> str | None:
|
|
122
|
+
"""Verify server.py can be imported successfully."""
|
|
123
|
+
try:
|
|
124
|
+
result = subprocess.run(
|
|
125
|
+
[sys.executable, "-c", "import server"],
|
|
126
|
+
cwd=str(SRC_DIR),
|
|
127
|
+
capture_output=True,
|
|
128
|
+
text=True,
|
|
129
|
+
timeout=15,
|
|
130
|
+
)
|
|
131
|
+
if result.returncode != 0:
|
|
132
|
+
return f"Import verification failed: {result.stderr or result.stdout}"
|
|
133
|
+
except Exception as e:
|
|
134
|
+
return f"Import verification error: {e}"
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def handle_update(remote: str = "origin", branch: str = "main") -> str:
|
|
139
|
+
"""Pull latest NEXO code, backup databases, run migrations, and verify.
|
|
140
|
+
|
|
141
|
+
Full update flow:
|
|
142
|
+
1. Check for uncommitted changes in src/
|
|
143
|
+
2. Backup all .db files
|
|
144
|
+
3. git pull
|
|
145
|
+
4. Run migrations if version changed
|
|
146
|
+
5. Verify server.py imports
|
|
147
|
+
6. Rollback on failure
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
remote: Git remote name (default: origin)
|
|
151
|
+
branch: Git branch to pull (default: main)
|
|
152
|
+
"""
|
|
153
|
+
steps_done = []
|
|
154
|
+
old_commit = None
|
|
155
|
+
backup_dir = None
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
# Step 1: Check dirty
|
|
159
|
+
dirty_err = _check_dirty()
|
|
160
|
+
if dirty_err:
|
|
161
|
+
return f"ABORTED: {dirty_err}"
|
|
162
|
+
steps_done.append("clean-check")
|
|
163
|
+
|
|
164
|
+
# Record current state
|
|
165
|
+
old_version = _read_version()
|
|
166
|
+
rc, old_commit, _ = _git("rev-parse", "HEAD")
|
|
167
|
+
if rc != 0:
|
|
168
|
+
return "ABORTED: Not a git repository or git not available."
|
|
169
|
+
|
|
170
|
+
# Step 2: Backup databases
|
|
171
|
+
backup_dir, backup_err = _backup_databases()
|
|
172
|
+
if backup_err:
|
|
173
|
+
return f"ABORTED at backup: {backup_err}"
|
|
174
|
+
steps_done.append("backup")
|
|
175
|
+
|
|
176
|
+
# Step 3: git pull
|
|
177
|
+
rc, pull_out, pull_err = _git("pull", remote, branch)
|
|
178
|
+
if rc != 0:
|
|
179
|
+
return f"ABORTED at git pull: {pull_err or pull_out}"
|
|
180
|
+
steps_done.append("git-pull")
|
|
181
|
+
|
|
182
|
+
# Step 4: Check version change
|
|
183
|
+
new_version = _read_version()
|
|
184
|
+
version_changed = old_version != new_version
|
|
185
|
+
|
|
186
|
+
# Step 5: Run migrations if version changed
|
|
187
|
+
if version_changed:
|
|
188
|
+
mig_err = _run_migrations()
|
|
189
|
+
if mig_err:
|
|
190
|
+
raise RuntimeError(f"Migration failed: {mig_err}")
|
|
191
|
+
steps_done.append("migrations")
|
|
192
|
+
|
|
193
|
+
# Step 6: Verify import
|
|
194
|
+
verify_err = _verify_import()
|
|
195
|
+
if verify_err:
|
|
196
|
+
raise RuntimeError(f"Verification failed: {verify_err}")
|
|
197
|
+
steps_done.append("verify")
|
|
198
|
+
|
|
199
|
+
# Build result
|
|
200
|
+
if pull_out == "Already up to date.":
|
|
201
|
+
return f"Already up to date (v{old_version}). No changes pulled."
|
|
202
|
+
|
|
203
|
+
lines = ["UPDATE SUCCESSFUL"]
|
|
204
|
+
if version_changed:
|
|
205
|
+
lines.append(f" Version: {old_version} -> {new_version}")
|
|
206
|
+
else:
|
|
207
|
+
lines.append(f" Version: {old_version} (unchanged)")
|
|
208
|
+
lines.append(f" Branch: {remote}/{branch}")
|
|
209
|
+
lines.append(f" Backup: {backup_dir}")
|
|
210
|
+
if version_changed:
|
|
211
|
+
lines.append(" Migrations: applied")
|
|
212
|
+
lines.append("")
|
|
213
|
+
lines.append("MCP server restart needed to load new code.")
|
|
214
|
+
return "\n".join(lines)
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
# Rollback
|
|
218
|
+
rollback_lines = [f"UPDATE FAILED: {e}", "", "Rolling back..."]
|
|
219
|
+
|
|
220
|
+
if old_commit and "git-pull" in steps_done:
|
|
221
|
+
rc, _, err = _git("reset", "--hard", old_commit)
|
|
222
|
+
if rc == 0:
|
|
223
|
+
rollback_lines.append(f" Git: reset to {old_commit[:8]}")
|
|
224
|
+
else:
|
|
225
|
+
rollback_lines.append(f" Git rollback FAILED: {err}")
|
|
226
|
+
|
|
227
|
+
if backup_dir and "backup" in steps_done:
|
|
228
|
+
_restore_databases(backup_dir)
|
|
229
|
+
rollback_lines.append(f" DBs: restored from {backup_dir}")
|
|
230
|
+
|
|
231
|
+
rollback_lines.append("")
|
|
232
|
+
rollback_lines.append("System restored to previous state.")
|
|
233
|
+
return "\n".join(rollback_lines)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
TOOLS = [
|
|
237
|
+
(handle_update, "nexo_update", "Pull latest NEXO code, backup DBs, run migrations, verify. Rolls back on failure."),
|
|
238
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# NEXO Brain — runtime dependencies
|
|
2
|
+
# Core (required)
|
|
3
|
+
fastmcp>=2.9.0
|
|
4
|
+
numpy
|
|
5
|
+
|
|
6
|
+
# Embedding model (optional but recommended for cognitive features)
|
|
7
|
+
fastembed
|
|
8
|
+
|
|
9
|
+
# Dashboard (optional, only needed for `python -m dashboard.app`)
|
|
10
|
+
fastapi
|
|
11
|
+
uvicorn
|
|
12
|
+
pydantic
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -175,13 +175,21 @@ Rules:
|
|
|
175
175
|
- Similar but different scope (e.g., different recipients) = NOT redundant
|
|
176
176
|
- When in doubt, say not redundant (false negatives are cheaper than false positives)"""
|
|
177
177
|
|
|
178
|
+
auth_check = subprocess.run(
|
|
179
|
+
[str(CLAUDE_CLI), "-p", "Reply with exactly: ok", "--bare", "--output-format", "text", "--model", "haiku"],
|
|
180
|
+
capture_output=True, text=True, timeout=15
|
|
181
|
+
)
|
|
182
|
+
if auth_check.returncode != 0:
|
|
183
|
+
# CLI not authenticated, skip gracefully
|
|
184
|
+
return {"redundant": False, "reason": "CLI not authenticated — skipped analysis", "suggestion": "N/A"}
|
|
185
|
+
|
|
178
186
|
env = os.environ.copy()
|
|
179
187
|
env.pop("CLAUDECODE", None)
|
|
180
188
|
env.pop("CLAUDE_CODE", None)
|
|
181
189
|
|
|
182
190
|
try:
|
|
183
191
|
result = subprocess.run(
|
|
184
|
-
[str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
|
|
192
|
+
[str(CLAUDE_CLI), "-p", prompt, "--model", "opus", "--output-format", "text", "--bare",
|
|
185
193
|
"--allowedTools", "Read,Write,Edit,Glob,Grep"],
|
|
186
194
|
capture_output=True, text=True, timeout=60, env=env
|
|
187
195
|
)
|
|
Binary file
|
|
Binary file
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import os
|
|
2
1
|
#!/usr/bin/env python3
|
|
3
2
|
"""
|
|
4
3
|
Deep Sleep — Step 3: Apply findings.
|
|
5
4
|
Takes the analysis output and writes feedback memories + trust adjustments.
|
|
6
5
|
"""
|
|
7
6
|
import json
|
|
7
|
+
import os
|
|
8
8
|
import sqlite3
|
|
9
9
|
import sys
|
|
10
10
|
from datetime import datetime
|
|
@@ -13,7 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
14
14
|
|
|
15
15
|
DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
|
|
16
|
-
NEXO_DB = NEXO_HOME / "
|
|
16
|
+
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def find_memory_dir() -> Path:
|
|
@@ -67,7 +67,7 @@ def update_memory_index(memory_dir: Path, new_entries: list[dict]):
|
|
|
67
67
|
|
|
68
68
|
def adjust_trust(points: int, context: str):
|
|
69
69
|
"""Record trust adjustment in cognitive.db if available."""
|
|
70
|
-
cog_db = NEXO_HOME / "
|
|
70
|
+
cog_db = NEXO_HOME / "data" / "cognitive.db"
|
|
71
71
|
if not cog_db.exists():
|
|
72
72
|
return
|
|
73
73
|
try:
|
|
@@ -1,213 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
NEXO Auto-Update — checks for new versions and applies updates.
|
|
4
|
-
|
|
5
|
-
Runs at boot via the catch-up system or manually.
|
|
6
|
-
Compares local version with the latest GitHub release.
|
|
7
|
-
If a new version is available, downloads and applies the update.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import os
|
|
12
|
-
import shutil
|
|
13
|
-
import subprocess
|
|
2
|
+
"""DEPRECATED: Updates are handled automatically by NEXO on startup."""
|
|
14
3
|
import sys
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
19
|
-
VERSION_FILE = NEXO_HOME / "version.json"
|
|
20
|
-
LOG_FILE = NEXO_HOME / "logs" / "auto-update.log"
|
|
21
|
-
REPO = "wazionapps/nexo"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def log(msg: str):
|
|
25
|
-
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
26
|
-
line = f"[{ts}] {msg}"
|
|
27
|
-
print(line, flush=True)
|
|
28
|
-
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
-
with open(LOG_FILE, "a") as f:
|
|
30
|
-
f.write(line + "\n")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def get_local_version() -> str:
|
|
34
|
-
"""Read locally installed version."""
|
|
35
|
-
if VERSION_FILE.exists():
|
|
36
|
-
try:
|
|
37
|
-
return json.loads(VERSION_FILE.read_text()).get("version", "0.0.0")
|
|
38
|
-
except Exception:
|
|
39
|
-
pass
|
|
40
|
-
return "0.0.0"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def get_remote_version() -> dict | None:
|
|
44
|
-
"""Check latest release on GitHub."""
|
|
45
|
-
try:
|
|
46
|
-
result = subprocess.run(
|
|
47
|
-
["gh", "api", f"repos/{REPO}/releases/latest"],
|
|
48
|
-
capture_output=True, text=True, timeout=15
|
|
49
|
-
)
|
|
50
|
-
if result.returncode == 0:
|
|
51
|
-
data = json.loads(result.stdout)
|
|
52
|
-
return {
|
|
53
|
-
"version": data.get("tag_name", "").lstrip("v"),
|
|
54
|
-
"tarball_url": data.get("tarball_url", ""),
|
|
55
|
-
"published_at": data.get("published_at", ""),
|
|
56
|
-
"body": data.get("body", "")[:500],
|
|
57
|
-
}
|
|
58
|
-
except Exception:
|
|
59
|
-
pass
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def version_compare(local: str, remote: str) -> int:
|
|
64
|
-
"""Compare semver strings. Returns -1 (local older), 0 (same), 1 (local newer)."""
|
|
65
|
-
def parse(v):
|
|
66
|
-
parts = v.split(".")
|
|
67
|
-
return tuple(int(p) for p in parts if p.isdigit())
|
|
68
|
-
|
|
69
|
-
l, r = parse(local), parse(remote)
|
|
70
|
-
if l < r:
|
|
71
|
-
return -1
|
|
72
|
-
elif l > r:
|
|
73
|
-
return 1
|
|
74
|
-
return 0
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def apply_update(tarball_url: str, new_version: str) -> bool:
|
|
78
|
-
"""Download and apply update from GitHub release tarball."""
|
|
79
|
-
import tempfile
|
|
80
|
-
|
|
81
|
-
log(f"Downloading update v{new_version}...")
|
|
82
|
-
tmp_dir = Path(tempfile.mkdtemp(prefix="nexo-update-"))
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
# Download tarball
|
|
86
|
-
tarball = tmp_dir / "release.tar.gz"
|
|
87
|
-
result = subprocess.run(
|
|
88
|
-
["curl", "-sL", "-o", str(tarball), tarball_url],
|
|
89
|
-
capture_output=True, timeout=60
|
|
90
|
-
)
|
|
91
|
-
if result.returncode != 0:
|
|
92
|
-
log(f"Download failed: {result.stderr}")
|
|
93
|
-
return False
|
|
94
|
-
|
|
95
|
-
# Extract
|
|
96
|
-
subprocess.run(
|
|
97
|
-
["tar", "xzf", str(tarball), "-C", str(tmp_dir)],
|
|
98
|
-
capture_output=True, timeout=30
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# Find extracted directory (GitHub tarballs have a top-level dir)
|
|
102
|
-
extracted = [d for d in tmp_dir.iterdir() if d.is_dir()]
|
|
103
|
-
if not extracted:
|
|
104
|
-
log("No directory found in tarball")
|
|
105
|
-
return False
|
|
106
|
-
|
|
107
|
-
src_dir = extracted[0] / "src"
|
|
108
|
-
if not src_dir.exists():
|
|
109
|
-
log("No src/ directory in release")
|
|
110
|
-
return False
|
|
111
|
-
|
|
112
|
-
# Backup current files
|
|
113
|
-
backup_dir = NEXO_HOME / "backups" / f"pre-update-{new_version}"
|
|
114
|
-
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
115
|
-
|
|
116
|
-
files_updated = 0
|
|
117
|
-
# Update core Python files
|
|
118
|
-
for py_file in src_dir.glob("*.py"):
|
|
119
|
-
dest = NEXO_HOME / py_file.name
|
|
120
|
-
if dest.exists():
|
|
121
|
-
shutil.copy2(dest, backup_dir / py_file.name)
|
|
122
|
-
shutil.copy2(py_file, dest)
|
|
123
|
-
files_updated += 1
|
|
124
|
-
|
|
125
|
-
# Update plugins
|
|
126
|
-
plugins_src = src_dir / "plugins"
|
|
127
|
-
if plugins_src.exists():
|
|
128
|
-
plugins_dest = NEXO_HOME / "plugins"
|
|
129
|
-
plugins_dest.mkdir(exist_ok=True)
|
|
130
|
-
for py_file in plugins_src.glob("*.py"):
|
|
131
|
-
dest = plugins_dest / py_file.name
|
|
132
|
-
if dest.exists():
|
|
133
|
-
shutil.copy2(dest, backup_dir / f"plugin_{py_file.name}")
|
|
134
|
-
shutil.copy2(py_file, dest)
|
|
135
|
-
files_updated += 1
|
|
136
|
-
|
|
137
|
-
# Update scripts
|
|
138
|
-
scripts_src = src_dir / "scripts"
|
|
139
|
-
if scripts_src.exists():
|
|
140
|
-
scripts_dest = NEXO_HOME / "scripts"
|
|
141
|
-
scripts_dest.mkdir(exist_ok=True)
|
|
142
|
-
for py_file in scripts_src.glob("*.py"):
|
|
143
|
-
dest = scripts_dest / py_file.name
|
|
144
|
-
if dest.exists():
|
|
145
|
-
shutil.copy2(dest, backup_dir / f"script_{py_file.name}")
|
|
146
|
-
shutil.copy2(py_file, dest)
|
|
147
|
-
files_updated += 1
|
|
148
|
-
|
|
149
|
-
# Update hooks
|
|
150
|
-
hooks_src = src_dir / "hooks"
|
|
151
|
-
if hooks_src.exists():
|
|
152
|
-
hooks_dest = NEXO_HOME / "hooks"
|
|
153
|
-
hooks_dest.mkdir(exist_ok=True)
|
|
154
|
-
for sh_file in hooks_src.glob("*.sh"):
|
|
155
|
-
dest = hooks_dest / sh_file.name
|
|
156
|
-
shutil.copy2(sh_file, dest)
|
|
157
|
-
os.chmod(dest, 0o755)
|
|
158
|
-
files_updated += 1
|
|
159
|
-
|
|
160
|
-
# Save new version
|
|
161
|
-
VERSION_FILE.write_text(json.dumps({
|
|
162
|
-
"version": new_version,
|
|
163
|
-
"updated_at": datetime.now().isoformat(),
|
|
164
|
-
"files_updated": files_updated,
|
|
165
|
-
"backup": str(backup_dir),
|
|
166
|
-
}, indent=2))
|
|
167
|
-
|
|
168
|
-
log(f"Update applied: {files_updated} files updated. Backup at {backup_dir}")
|
|
169
|
-
return True
|
|
170
|
-
|
|
171
|
-
except Exception as e:
|
|
172
|
-
log(f"Update failed: {e}")
|
|
173
|
-
return False
|
|
174
|
-
finally:
|
|
175
|
-
shutil.rmtree(tmp_dir, ignore_errors=True)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def main():
|
|
179
|
-
log("=== NEXO Auto-Update check ===")
|
|
180
|
-
|
|
181
|
-
local_ver = get_local_version()
|
|
182
|
-
log(f"Local version: {local_ver}")
|
|
183
|
-
|
|
184
|
-
remote = get_remote_version()
|
|
185
|
-
if not remote:
|
|
186
|
-
log("Could not check remote version (no network or no releases)")
|
|
187
|
-
return
|
|
188
|
-
|
|
189
|
-
remote_ver = remote["version"]
|
|
190
|
-
log(f"Remote version: {remote_ver}")
|
|
191
|
-
|
|
192
|
-
cmp = version_compare(local_ver, remote_ver)
|
|
193
|
-
if cmp >= 0:
|
|
194
|
-
log("Already up to date.")
|
|
195
|
-
return
|
|
196
|
-
|
|
197
|
-
log(f"Update available: {local_ver} → {remote_ver}")
|
|
198
|
-
log(f"Release notes: {remote['body'][:200]}")
|
|
199
|
-
|
|
200
|
-
if remote["tarball_url"]:
|
|
201
|
-
success = apply_update(remote["tarball_url"], remote_ver)
|
|
202
|
-
if success:
|
|
203
|
-
log(f"Successfully updated to v{remote_ver}")
|
|
204
|
-
else:
|
|
205
|
-
log("Update failed — will retry next boot")
|
|
206
|
-
else:
|
|
207
|
-
log("No tarball URL in release — manual update needed")
|
|
208
|
-
|
|
209
|
-
log("=== Done ===")
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if __name__ == "__main__":
|
|
213
|
-
main()
|
|
4
|
+
print("This script is deprecated. NEXO auto-updates on startup.")
|
|
5
|
+
print("To update manually, use the nexo_update MCP tool.")
|
|
6
|
+
sys.exit(0)
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# NEXO DB hourly backup
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
NEXO_DIR="${NEXO_DIR:-$(dirname "$(dirname "$(realpath "$0")")")}"
|
|
8
|
-
BACKUP_DIR="$NEXO_DIR/backups"
|
|
2
|
+
# NEXO DB hourly backup — crontab: 0 * * * * $NEXO_HOME/scripts/nexo-backup.sh
|
|
3
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
4
|
+
NEXO_DIR="$NEXO_HOME"
|
|
5
|
+
BACKUP_DIR="$NEXO_HOME/backups"
|
|
9
6
|
WEEKLY_DIR="$BACKUP_DIR/weekly"
|
|
10
|
-
DB="$
|
|
7
|
+
DB="$NEXO_HOME/data/nexo.db"
|
|
11
8
|
RETENTION_HOURS=48
|
|
12
9
|
|
|
13
|
-
if [ ! -f "$DB" ]; then
|
|
14
|
-
echo "ERROR: nexo.db not found at $DB" >&2
|
|
15
|
-
exit 1
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
10
|
mkdir -p "$BACKUP_DIR" "$WEEKLY_DIR"
|
|
19
11
|
|
|
20
12
|
# Hourly backup
|