nexo-brain 1.7.0 → 2.1.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.
Files changed (261) hide show
  1. package/README.md +160 -60
  2. package/bin/nexo-brain.js +680 -381
  3. package/package.json +18 -3
  4. package/scripts/migrate-to-unified.sh +813 -0
  5. package/scripts/migrate-v1.7-to-v1.8.py +214 -0
  6. package/scripts/pre-commit-check.sh +1 -1
  7. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  8. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  9. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  10. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  11. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  12. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  13. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  14. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  15. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  16. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  17. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  18. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  19. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  20. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  21. package/src/auto_close_sessions.py +1 -1
  22. package/src/auto_update.py +634 -0
  23. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  24. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  26. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  27. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  28. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  29. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  30. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  31. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  32. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  33. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  34. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  35. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  36. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  37. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  38. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  39. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  40. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  41. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  42. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  43. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  44. package/src/cognitive/_core.py +7 -3
  45. package/src/cognitive/_decay.py +1 -1
  46. package/src/cognitive/_search.py +1 -0
  47. package/src/cognitive/_trust.py +3 -3
  48. package/src/crons/manifest.json +106 -0
  49. package/src/crons/sync.py +217 -0
  50. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  51. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  52. package/src/dashboard/app.py +24 -4
  53. package/src/dashboard/templates/dashboard.html +3 -2
  54. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  55. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  56. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  57. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  58. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  59. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  60. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  61. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  62. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  63. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  64. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  65. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  66. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  67. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  68. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  69. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  70. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  71. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  72. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  73. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  74. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  75. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  76. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  77. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  78. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  79. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  80. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  81. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  82. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  83. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  84. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  85. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  86. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  87. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  90. package/src/db/_core.py +5 -1
  91. package/src/db/_episodic.py +2 -4
  92. package/src/db/_reminders.py +45 -6
  93. package/src/db/_schema.py +31 -0
  94. package/src/evolution_cycle.py +33 -11
  95. package/src/hooks/auto_capture.py +1 -1
  96. package/src/hooks/capture-tool-logs.sh +76 -0
  97. package/src/hooks/inbox-hook.sh +2 -1
  98. package/src/hooks/post-compact.sh +2 -1
  99. package/src/hooks/pre-compact.sh +104 -2
  100. package/src/hooks/session-start.sh +6 -2
  101. package/src/hooks/session-stop.sh +4 -2
  102. package/src/kg_populate.py +4 -1
  103. package/src/migrate_embeddings.py +4 -1
  104. package/src/plugin_loader.py +100 -34
  105. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  106. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  107. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  108. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  109. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  110. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  111. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  112. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  113. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  114. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  115. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  116. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  117. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  118. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  119. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  120. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  121. package/src/plugins/agents.py +2 -2
  122. package/src/plugins/backup.py +5 -4
  123. package/src/plugins/core_rules.py +37 -16
  124. package/src/plugins/episodic_memory.py +14 -5
  125. package/src/plugins/evolution.py +6 -2
  126. package/src/plugins/guard.py +20 -11
  127. package/src/plugins/update.py +256 -0
  128. package/src/requirements.txt +12 -0
  129. package/src/scripts/check-context.py +8 -3
  130. package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
  131. package/src/scripts/deep-sleep/apply_findings.py +514 -169
  132. package/src/scripts/deep-sleep/collect.py +480 -0
  133. package/src/scripts/deep-sleep/extract-prompt.md +233 -0
  134. package/src/scripts/deep-sleep/extract.py +249 -0
  135. package/src/scripts/deep-sleep/synthesize-prompt.md +168 -0
  136. package/src/scripts/deep-sleep/synthesize.py +191 -0
  137. package/src/scripts/nexo-auto-update.py +4 -211
  138. package/src/scripts/nexo-backup.sh +5 -13
  139. package/src/scripts/nexo-brain-activation.sh +26 -26
  140. package/src/scripts/nexo-catchup.py +36 -25
  141. package/src/scripts/nexo-cognitive-decay.py +7 -3
  142. package/src/scripts/nexo-daily-self-audit.py +44 -15
  143. package/src/scripts/nexo-deep-sleep.sh +31 -16
  144. package/src/scripts/nexo-evolution-run.py +21 -11
  145. package/src/scripts/nexo-followup-hygiene.py +6 -4
  146. package/src/scripts/nexo-github-monitor.py +12 -8
  147. package/src/scripts/nexo-immune.py +6 -4
  148. package/src/scripts/nexo-inbox-hook.sh +2 -1
  149. package/src/scripts/nexo-install.py +4 -225
  150. package/src/scripts/nexo-learning-housekeep.py +7 -3
  151. package/src/scripts/nexo-learning-validator.py +1 -22
  152. package/src/scripts/nexo-migrate.py +9 -3
  153. package/src/scripts/nexo-postmortem-consolidator.py +17 -10
  154. package/src/scripts/nexo-pre-commit.py +3 -1
  155. package/src/scripts/nexo-prevent-sleep.sh +29 -0
  156. package/src/scripts/nexo-proactive-dashboard.py +5 -4
  157. package/src/scripts/nexo-runtime-preflight.py +59 -55
  158. package/src/scripts/nexo-send-email.py +1 -1
  159. package/src/scripts/nexo-send-reply.py +3 -1
  160. package/src/scripts/nexo-sleep.py +13 -9
  161. package/src/scripts/nexo-snapshot-restore.sh +2 -1
  162. package/src/scripts/nexo-synthesis.py +11 -7
  163. package/src/scripts/nexo-tcc-approve.sh +79 -0
  164. package/src/scripts/nexo-update.sh +161 -0
  165. package/src/scripts/nexo-watchdog-smoke.py +18 -13
  166. package/src/scripts/nexo-watchdog.sh +22 -13
  167. package/src/server.py +77 -28
  168. package/src/storage_router.py +6 -2
  169. package/src/tools_learnings.py +6 -6
  170. package/src/tools_menu.py +1 -1
  171. package/src/tools_reminders_crud.py +10 -8
  172. package/src/tools_sessions.py +76 -4
  173. package/templates/CLAUDE.md.template +14 -80
  174. package/templates/launchagents/README.md +7 -7
  175. package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
  176. package/templates/launchagents/com.nexo.catchup.plist +4 -0
  177. package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
  178. package/templates/launchagents/com.nexo.dashboard.plist +5 -1
  179. package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
  180. package/templates/launchagents/com.nexo.evolution.plist +4 -0
  181. package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
  182. package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
  183. package/templates/launchagents/com.nexo.immune.plist +4 -0
  184. package/templates/launchagents/com.nexo.postmortem.plist +4 -0
  185. package/templates/launchagents/com.nexo.self-audit.plist +4 -0
  186. package/templates/launchagents/com.nexo.synthesis.plist +4 -0
  187. package/templates/launchagents/com.nexo.watchdog.plist +4 -0
  188. package/templates/openclaw.json +1 -1
  189. package/tests/conftest.py +2 -2
  190. package/tests/test_cognitive.py +7 -6
  191. package/tests/test_migrations.py +26 -0
  192. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  193. package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
  194. package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
  195. package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
  196. package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  197. package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
  198. package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
  199. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  200. package/src/__pycache__/server.cpython-314.pyc +0 -0
  201. package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
  202. package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
  203. package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
  204. package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
  205. package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
  206. package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
  207. package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
  208. package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
  209. package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
  210. package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
  212. package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
  213. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  214. package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
  215. package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
  216. package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
  217. package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
  218. package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
  219. package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
  220. package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
  221. package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
  222. package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
  223. package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
  224. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
  225. package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
  226. package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
  227. package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  244. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  245. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  246. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  247. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  248. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  249. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  250. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  251. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
  252. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
  253. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
  254. package/src/scripts/deep-sleep/analyze_session.py +0 -217
  255. package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
  256. package/src/scripts/deep-sleep/prompt.md +0 -109
  257. package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  259. package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
  260. package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
  261. package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -0,0 +1,256 @@
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
+ # Step 7: Sync crons with manifest
200
+ cron_sync_result = ""
201
+ try:
202
+ cron_sync_path = NEXO_CODE / "crons" / "sync.py"
203
+ if cron_sync_path.exists():
204
+ import subprocess as _sp
205
+ r = _sp.run(
206
+ [sys.executable, str(cron_sync_path)],
207
+ capture_output=True, text=True, timeout=30,
208
+ env={**os.environ, "NEXO_HOME": str(NEXO_HOME), "NEXO_CODE": str(NEXO_CODE)},
209
+ )
210
+ cron_sync_result = r.stdout.strip()
211
+ steps_done.append("cron-sync")
212
+ except Exception as e:
213
+ cron_sync_result = f"Cron sync warning: {e}"
214
+
215
+ # Build result
216
+ if pull_out == "Already up to date.":
217
+ return f"Already up to date (v{old_version}). No changes pulled."
218
+
219
+ lines = ["UPDATE SUCCESSFUL"]
220
+ if version_changed:
221
+ lines.append(f" Version: {old_version} -> {new_version}")
222
+ else:
223
+ lines.append(f" Version: {old_version} (unchanged)")
224
+ lines.append(f" Branch: {remote}/{branch}")
225
+ lines.append(f" Backup: {backup_dir}")
226
+ if version_changed:
227
+ lines.append(" Migrations: applied")
228
+ if "cron-sync" in steps_done:
229
+ lines.append(" Crons: synced with manifest")
230
+ lines.append("")
231
+ lines.append("MCP server restart needed to load new code.")
232
+ return "\n".join(lines)
233
+
234
+ except Exception as e:
235
+ # Rollback
236
+ rollback_lines = [f"UPDATE FAILED: {e}", "", "Rolling back..."]
237
+
238
+ if old_commit and "git-pull" in steps_done:
239
+ rc, _, err = _git("reset", "--hard", old_commit)
240
+ if rc == 0:
241
+ rollback_lines.append(f" Git: reset to {old_commit[:8]}")
242
+ else:
243
+ rollback_lines.append(f" Git rollback FAILED: {err}")
244
+
245
+ if backup_dir and "backup" in steps_done:
246
+ _restore_databases(backup_dir)
247
+ rollback_lines.append(f" DBs: restored from {backup_dir}")
248
+
249
+ rollback_lines.append("")
250
+ rollback_lines.append("System restored to previous state.")
251
+ return "\n".join(rollback_lines)
252
+
253
+
254
+ TOOLS = [
255
+ (handle_update, "nexo_update", "Pull latest NEXO code, backup DBs, run migrations, verify. Rolls back on failure."),
256
+ ]
@@ -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
@@ -174,16 +174,21 @@ Rules:
174
174
  - Same file modification with same content = redundant
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
+ )
178
+ if auth_check.returncode != 0:
179
+ # CLI not authenticated, skip gracefully
180
+ return {"redundant": False, "reason": "CLI not authenticated — skipped analysis", "suggestion": "N/A"}
177
181
 
178
182
  env = os.environ.copy()
183
+ env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
179
184
  env.pop("CLAUDECODE", None)
180
185
  env.pop("CLAUDE_CODE", None)
181
186
 
182
187
  try:
183
188
  result = subprocess.run(
184
- [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
185
- "--allowedTools", "Read,Write,Edit,Glob,Grep"],
186
- capture_output=True, text=True, timeout=60, env=env
189
+ [str(CLAUDE_CLI), "-p", prompt, "--model", "opus", "--output-format", "text",
190
+ "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
191
+ capture_output=True, text=True, timeout=21600, env=env
187
192
  )
188
193
  if result.returncode == 0:
189
194
  text = result.stdout.strip()