nexo-brain 2.3.0 → 2.3.2

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 (299) hide show
  1. package/README.md +1 -1
  2. package/bin/nexo-brain.js +92 -9
  3. package/bin/postinstall.js +22 -15
  4. package/package.json +7 -4
  5. package/src/auto_update.py +194 -5
  6. package/src/crons/sync.py +6 -2
  7. package/src/db/_core.py +1 -0
  8. package/src/db/_entities.py +1 -0
  9. package/src/db/_episodic.py +1 -0
  10. package/src/db/_learnings.py +1 -0
  11. package/src/db/_reminders.py +1 -0
  12. package/src/db/_schema.py +11 -1
  13. package/src/db/_sessions.py +1 -0
  14. package/src/db/_skills.py +1 -0
  15. package/src/hooks/capture-tool-logs.sh +23 -6
  16. package/src/hooks/session-start.sh +4 -3
  17. package/src/plugin_loader.py +1 -0
  18. package/src/plugins/update.py +377 -26
  19. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  20. package/src/scripts/deep-sleep/collect.py +1 -0
  21. package/src/scripts/deep-sleep/extract.py +1 -0
  22. package/src/scripts/deep-sleep/synthesize.py +1 -0
  23. package/src/scripts/nexo-catchup.py +29 -4
  24. package/src/scripts/nexo-daily-self-audit.py +21 -1
  25. package/src/scripts/nexo-evolution-run.py +21 -1
  26. package/src/scripts/nexo-learning-housekeep.py +1 -0
  27. package/src/scripts/nexo-postmortem-consolidator.py +34 -9
  28. package/src/scripts/nexo-sleep.py +32 -10
  29. package/src/scripts/nexo-synthesis.py +29 -9
  30. package/src/scripts/nexo-update.sh +109 -7
  31. package/src/scripts/nexo-watchdog.sh +122 -58
  32. package/src/server.py +66 -1
  33. package/src/tools_coordination.py +1 -0
  34. package/src/tools_sessions.py +1 -0
  35. package/scripts/migrate-to-unified 2.sh +0 -813
  36. package/scripts/migrate-to-unified.sh +0 -813
  37. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  38. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  39. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  40. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  41. package/scripts/nexo-preflight.sh +0 -236
  42. package/scripts/pre-commit-check 2.sh +0 -55
  43. package/scripts/pre-commit-check.sh +0 -55
  44. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  45. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  46. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  47. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  48. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  49. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  50. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  51. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  52. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  53. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  54. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  55. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  56. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  57. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  58. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  59. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  60. package/src/auto_close_sessions 2.py +0 -159
  61. package/src/auto_update 2.py +0 -634
  62. package/src/claim_graph 2.py +0 -323
  63. package/src/cognitive/__init__ 2.py +0 -62
  64. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  73. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  74. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  75. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  76. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  77. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  78. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  79. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  80. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  81. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  82. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  83. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  84. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  85. package/src/cognitive/_core 2.py +0 -567
  86. package/src/cognitive/_decay 2.py +0 -382
  87. package/src/cognitive/_ingest 2.py +0 -892
  88. package/src/cognitive/_memory 2.py +0 -912
  89. package/src/cognitive/_search 2.py +0 -949
  90. package/src/cognitive/_trust 2.py +0 -464
  91. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  92. package/src/crons/manifest 2.json +0 -106
  93. package/src/crons/sync 2.py +0 -217
  94. package/src/dashboard/__init__ 2.py +0 -0
  95. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  96. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  97. package/src/dashboard/app 2.py +0 -789
  98. package/src/db/__init__ 2.py +0 -89
  99. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  128. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  129. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  130. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  131. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  132. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  133. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  134. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  135. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  136. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  137. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  138. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  139. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  140. package/src/db/_core 2.py +0 -417
  141. package/src/db/_credentials 2.py +0 -124
  142. package/src/db/_entities 2.py +0 -178
  143. package/src/db/_episodic 2.py +0 -738
  144. package/src/db/_evolution 2.py +0 -54
  145. package/src/db/_fts 2.py +0 -406
  146. package/src/db/_learnings 2.py +0 -168
  147. package/src/db/_reminders 2.py +0 -338
  148. package/src/db/_schema 2.py +0 -364
  149. package/src/db/_sessions 2.py +0 -300
  150. package/src/db/_tasks 2.py +0 -91
  151. package/src/evolution_cycle 2.py +0 -266
  152. package/src/hnsw_index 2.py +0 -254
  153. package/src/hooks/auto_capture 2.py +0 -208
  154. package/src/hooks/caffeinate-guard 2.sh +0 -8
  155. package/src/hooks/capture-session 2.sh +0 -21
  156. package/src/hooks/capture-tool-logs 2.sh +0 -127
  157. package/src/hooks/daily-briefing-check 2.sh +0 -33
  158. package/src/hooks/inbox-hook 2.sh +0 -76
  159. package/src/hooks/post-compact 2.sh +0 -148
  160. package/src/hooks/pre-compact 2.sh +0 -151
  161. package/src/hooks/session-start 2.sh +0 -268
  162. package/src/hooks/session-stop 2.sh +0 -140
  163. package/src/kg_populate 2.py +0 -290
  164. package/src/knowledge_graph 2.py +0 -257
  165. package/src/maintenance 2.py +0 -59
  166. package/src/migrate_embeddings 2.py +0 -122
  167. package/src/plugin_loader 2.py +0 -202
  168. package/src/plugins/__init__ 2.py +0 -0
  169. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  172. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  175. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  189. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  191. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  193. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  194. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  195. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  196. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  197. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  198. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  199. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  200. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  201. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  202. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  203. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  204. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  205. package/src/plugins/adaptive_mode 2.py +0 -805
  206. package/src/plugins/agents 2.py +0 -52
  207. package/src/plugins/artifact_registry 2.py +0 -450
  208. package/src/plugins/backup 2.py +0 -104
  209. package/src/plugins/cognitive_memory 2.py +0 -564
  210. package/src/plugins/core_rules 2.py +0 -252
  211. package/src/plugins/cortex 2.py +0 -299
  212. package/src/plugins/entities 2.py +0 -67
  213. package/src/plugins/episodic_memory 2.py +0 -533
  214. package/src/plugins/evolution 2.py +0 -115
  215. package/src/plugins/guard 2.py +0 -746
  216. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  217. package/src/plugins/preferences 2.py +0 -47
  218. package/src/plugins/update 2.py +0 -256
  219. package/src/requirements 2.txt +0 -12
  220. package/src/rules/__init__ 2.py +0 -0
  221. package/src/rules/core-rules 2.json +0 -331
  222. package/src/rules/migrate 2.py +0 -207
  223. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  244. package/src/scripts/check-context 2.py +0 -264
  245. package/src/scripts/nexo-auto-update 2.py +0 -6
  246. package/src/scripts/nexo-backup 2.sh +0 -25
  247. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  248. package/src/scripts/nexo-catchup 2.py +0 -242
  249. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  250. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  251. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  252. package/src/scripts/nexo-evolution-run 2.py +0 -597
  253. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  254. package/src/scripts/nexo-github-monitor 2.py +0 -256
  255. package/src/scripts/nexo-immune 2.py +0 -927
  256. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  257. package/src/scripts/nexo-install 2.py +0 -6
  258. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  259. package/src/scripts/nexo-learning-validator 2.py +0 -207
  260. package/src/scripts/nexo-migrate 2.py +0 -232
  261. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  262. package/src/scripts/nexo-pre-commit 2.py +0 -120
  263. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  264. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  265. package/src/scripts/nexo-reflection 2.py +0 -253
  266. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  267. package/src/scripts/nexo-send-email 2.py +0 -25
  268. package/src/scripts/nexo-send-email.py +0 -25
  269. package/src/scripts/nexo-send-reply 2.py +0 -178
  270. package/src/scripts/nexo-send-reply.py +0 -178
  271. package/src/scripts/nexo-sleep 2.py +0 -592
  272. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  273. package/src/scripts/nexo-synthesis 2.py +0 -253
  274. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  275. package/src/scripts/nexo-update 2.sh +0 -161
  276. package/src/scripts/nexo-watchdog 2.sh +0 -878
  277. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  278. package/src/server 2.py +0 -733
  279. package/src/storage_router 2.py +0 -32
  280. package/src/tools_coordination 2.py +0 -102
  281. package/src/tools_credentials 2.py +0 -68
  282. package/src/tools_learnings 2.py +0 -220
  283. package/src/tools_menu 2.py +0 -227
  284. package/src/tools_reminders 2.py +0 -86
  285. package/src/tools_reminders_crud 2.py +0 -159
  286. package/src/tools_sessions 2.py +0 -476
  287. package/src/tools_task_history 2.py +0 -57
  288. package/templates/CLAUDE.md 2.template +0 -63
  289. package/templates/openclaw 2.json +0 -13
  290. package/tests/__init__ 2.py +0 -0
  291. package/tests/__init__.py +0 -0
  292. package/tests/conftest 2.py +0 -71
  293. package/tests/conftest.py +0 -71
  294. package/tests/test_cognitive 2.py +0 -205
  295. package/tests/test_cognitive.py +0 -205
  296. package/tests/test_knowledge_graph 2.py +0 -140
  297. package/tests/test_knowledge_graph.py +0 -140
  298. package/tests/test_migrations 2.py +0 -137
  299. package/tests/test_migrations.py +0 -137
@@ -45,7 +45,12 @@ log() { echo "[$TS] $1" >> "$LOG"; }
45
45
  # The NEXO_CODE env var must point to the repo src/ directory.
46
46
  # Add personal (non-manifest) monitors to PERSONAL_MONITORS below.
47
47
  NEXO_CODE="${NEXO_CODE:-$(cd "$(dirname "$0")/.." 2>/dev/null && pwd)}"
48
- MANIFEST_FILE="$NEXO_CODE/crons/manifest.json"
48
+ # Look for manifest in NEXO_HOME first (packaged install), then NEXO_CODE (dev/repo)
49
+ if [ -f "$NEXO_HOME/crons/manifest.json" ]; then
50
+ MANIFEST_FILE="$NEXO_HOME/crons/manifest.json"
51
+ else
52
+ MANIFEST_FILE="$NEXO_CODE/crons/manifest.json"
53
+ fi
49
54
 
50
55
  _build_monitors_from_manifest() {
51
56
  if [ ! -f "$MANIFEST_FILE" ]; then
@@ -53,18 +58,22 @@ _build_monitors_from_manifest() {
53
58
  return
54
59
  fi
55
60
  python3 -c "
56
- import json, sys
61
+ import json, sys, platform
57
62
 
58
63
  nexo_home = '$NEXO_HOME'
64
+ is_mac = platform.system() == 'Darwin'
59
65
 
60
66
  with open('$MANIFEST_FILE') as f:
61
67
  data = json.load(f)
62
68
 
63
69
  for c in data.get('crons', []):
64
70
  cid = c['id']
65
- # Derive human-readable name from id
66
71
  name = cid.replace('-', ' ').title()
67
- plist_id = 'com.nexo.' + cid
72
+ # Use the right service identifier per platform
73
+ if is_mac:
74
+ svc_id = 'com.nexo.' + cid
75
+ else:
76
+ svc_id = 'nexo-' + cid + '.timer'
68
77
  stdout_log = nexo_home + '/logs/' + cid + '-stdout.log'
69
78
  stderr_log = nexo_home + '/logs/' + cid + '-stderr.log'
70
79
 
@@ -98,7 +107,7 @@ for c in data.get('crons', []):
98
107
  mon_type = 'core' if c.get('core') else 'personal'
99
108
  proc_grep = '' # manifest crons are one-shot, no persistent process
100
109
 
101
- print(f'{name}|{plist_id}|{stdout_log}|{stderr_log}|{max_stale}|{proc_grep}|{schedule_desc}|{mon_type}')
110
+ print(f'{name}|{svc_id}|{stdout_log}|{stderr_log}|{max_stale}|{proc_grep}|{schedule_desc}|{mon_type}')
102
111
  " 2>/dev/null
103
112
  }
104
113
 
@@ -115,9 +124,14 @@ PERSONAL_MONITORS=(
115
124
  MONITORS+=("${PERSONAL_MONITORS[@]+"${PERSONAL_MONITORS[@]}"}")
116
125
 
117
126
  # Cron jobs to check (NAME|SCRIPT|CHECK_PATH|MAX_STALE_SECS|SCHEDULE)
118
- CRON_MONITORS=(
119
- "Backup Cron|$NEXO_DIR/backup_cron.sh|$NEXO_DIR/backups/|7200|Hourly"
120
- )
127
+ # Core cron monitors are loaded from manifest above.
128
+ # Maintainer-only monitors go here (guarded by NEXO_MAINTAINER env var).
129
+ CRON_MONITORS=()
130
+ if [ "${NEXO_MAINTAINER:-}" = "1" ]; then
131
+ CRON_MONITORS+=(
132
+ "Backup|$NEXO_DIR/scripts/nexo-backup.sh|$NEXO_DIR/backups/|7200|Hourly"
133
+ )
134
+ fi
121
135
 
122
136
  # Error patterns to search in stderr logs (last 50 lines)
123
137
  ERROR_PATTERNS="Traceback|Error:|CRITICAL|FATAL|ModuleNotFoundError|PermissionError|FileNotFoundError|ConnectionRefused|Errno"
@@ -135,7 +149,12 @@ IS_MACOS=false
135
149
  log_repair() { echo "[$TS] REPAIR: $1" >> "$REPAIR_LOG"; log "REPAIR: $1"; }
136
150
 
137
151
  is_loaded() {
138
- $IS_MACOS && launchctl list "$1" &>/dev/null
152
+ if $IS_MACOS; then
153
+ launchctl list "$1" &>/dev/null
154
+ else
155
+ # On Linux, check if the systemd timer is enabled
156
+ systemctl --user is-enabled "$1" &>/dev/null
157
+ fi
139
158
  }
140
159
 
141
160
  # ============================================================================
@@ -174,6 +193,36 @@ try_repair_launchagent() {
174
193
  return 1
175
194
  }
176
195
 
196
+ try_repair_systemd() {
197
+ $IS_MACOS && return 1
198
+ local timer_unit="$1"
199
+ local service_unit="${timer_unit%.timer}.service"
200
+
201
+ # Repair 1: Timer not enabled — try to enable and start
202
+ if ! systemctl --user is-enabled "$timer_unit" &>/dev/null; then
203
+ systemctl --user daemon-reload 2>/dev/null
204
+ systemctl --user enable --now "$timer_unit" 2>/dev/null
205
+ sleep 1
206
+ if systemctl --user is-enabled "$timer_unit" &>/dev/null; then
207
+ log_repair "$timer_unit: enabled and started"
208
+ return 0
209
+ fi
210
+ return 1
211
+ fi
212
+
213
+ # Repair 2: Timer enabled but not active — start it
214
+ if ! systemctl --user is-active "$timer_unit" &>/dev/null; then
215
+ systemctl --user start "$timer_unit" 2>/dev/null
216
+ sleep 1
217
+ if systemctl --user is-active "$timer_unit" &>/dev/null; then
218
+ log_repair "$timer_unit: restarted"
219
+ return 0
220
+ fi
221
+ fi
222
+
223
+ return 1
224
+ }
225
+
177
226
  try_repair_cron() {
178
227
  local script="$1"
179
228
 
@@ -190,29 +239,26 @@ try_repair_cron() {
190
239
  }
191
240
 
192
241
  try_reexecute_missed_cron() {
193
- $IS_MACOS || return 1
194
- # Re-execute a cron that missed its scheduled run
195
- # Extracts ProgramArguments from the plist and runs them
196
- local plist_id="$1"
197
- local plist_file="$HOME_DIR/Library/LaunchAgents/${plist_id}.plist"
242
+ local svc_id="$1"
198
243
 
199
- if [ ! -f "$plist_file" ]; then
200
- log "Re-execute skipped: no plist for $plist_id"
201
- return 1
202
- fi
244
+ if $IS_MACOS; then
245
+ # macOS: extract command from plist and run it
246
+ local plist_file="$HOME_DIR/Library/LaunchAgents/${svc_id}.plist"
203
247
 
204
- # Extract the full command from plist
205
- local cmd
206
- cmd=$(python3 -c "
248
+ if [ ! -f "$plist_file" ]; then
249
+ log "Re-execute skipped: no plist for $svc_id"
250
+ return 1
251
+ fi
252
+
253
+ local cmd
254
+ cmd=$(python3 -c "
207
255
  import plistlib, sys
208
256
  try:
209
257
  with open('$plist_file', 'rb') as f:
210
258
  d = plistlib.load(f)
211
259
  args = d.get('ProgramArguments', [])
212
- # Skip KeepAlive services (they should be running, not re-executed)
213
260
  if d.get('KeepAlive'):
214
261
  sys.exit(1)
215
- # Skip services without a schedule (RunAtLoad only)
216
262
  if not d.get('StartCalendarInterval') and not d.get('StartInterval'):
217
263
  sys.exit(1)
218
264
  print(' '.join(args))
@@ -220,28 +266,36 @@ except:
220
266
  sys.exit(1)
221
267
  " 2>/dev/null)
222
268
 
223
- if [ -z "$cmd" ] || [ $? -ne 0 ]; then
224
- return 1
225
- fi
226
-
227
- log "Re-executing missed cron: $plist_id → $cmd"
228
- # Run in background with timeout (5 min max)
229
- timeout 300 bash -c "$cmd" >> "$LOG_DIR/watchdog-reexec.log" 2>&1 &
230
- local pid=$!
269
+ if [ -z "$cmd" ] || [ $? -ne 0 ]; then
270
+ return 1
271
+ fi
231
272
 
232
- # Wait briefly and check if it started ok
233
- sleep 2
234
- if kill -0 "$pid" 2>/dev/null || wait "$pid" 2>/dev/null; then
235
- log_repair "$plist_id: re-executed missed cron (PID $pid)"
236
- return 0
273
+ log "Re-executing missed cron: $svc_id $cmd"
274
+ timeout 300 bash -c "$cmd" >> "$LOG_DIR/watchdog-reexec.log" 2>&1 &
275
+ local pid=$!
276
+ sleep 2
277
+ if kill -0 "$pid" 2>/dev/null || wait "$pid" 2>/dev/null; then
278
+ log_repair "$svc_id: re-executed missed cron (PID $pid)"
279
+ return 0
280
+ else
281
+ log "Re-execute failed for $svc_id"
282
+ return 1
283
+ fi
237
284
  else
238
- log "Re-execute failed for $plist_id"
239
- return 1
285
+ # Linux: start the corresponding service unit directly
286
+ local service_unit="${svc_id%.timer}.service"
287
+ log "Re-executing missed cron: $svc_id → systemctl start $service_unit"
288
+ if systemctl --user start "$service_unit" 2>/dev/null; then
289
+ log_repair "$svc_id: re-executed via systemctl start $service_unit"
290
+ return 0
291
+ else
292
+ log "Re-execute failed for $svc_id"
293
+ return 1
294
+ fi
240
295
  fi
241
296
  }
242
297
 
243
298
  try_verify_repair() {
244
- $IS_MACOS || return 1
245
299
  # After Level 2 repair, wait and verify the service is healthy
246
300
  local plist_id="$1"
247
301
  local log_stdout="$2"
@@ -287,16 +341,18 @@ try_verify_repair() {
287
341
  }
288
342
 
289
343
  try_repair_backup() {
290
- local backup_script="$NEXO_DIR/backup_cron.sh"
344
+ # Use the core backup script (nexo-backup.sh)
345
+ local backup_script="$NEXO_DIR/scripts/nexo-backup.sh"
346
+ [ ! -x "$backup_script" ] && backup_script="$SCRIPT_DIR/nexo-backup.sh"
291
347
  if [ -x "$backup_script" ]; then
292
- "$backup_script" 2>/dev/null
348
+ bash "$backup_script" 2>/dev/null
293
349
  sleep 1
294
350
  local newest
295
351
  newest=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
296
352
  if [ -n "$newest" ]; then
297
353
  if $IS_MACOS; then local age=$(( TS_EPOCH - $(stat -f %m "$newest") )); else local age=$(( TS_EPOCH - $(stat -c %Y "$newest") )); fi
298
354
  if [ "$age" -lt 60 ]; then
299
- log_repair "backup_cron.sh: ran successfully, fresh backup created"
355
+ log_repair "nexo-backup.sh: ran successfully, fresh backup created"
300
356
  return 0
301
357
  fi
302
358
  fi
@@ -381,20 +437,26 @@ for monitor in "${MONITORS[@]}"; do
381
437
  error_count=0
382
438
  proc_alive="n/a"
383
439
 
384
- # Check 1: LaunchAgent loaded?
440
+ # Check 1: Service loaded? (launchd on macOS, systemd on Linux)
385
441
  if is_loaded "$plist_id"; then
386
442
  loaded="yes"
387
443
  else
388
444
  loaded="no"
389
- # AUTO-REPAIR: try to bootstrap
390
- if try_repair_launchagent "$plist_id" "$proc_grep"; then
445
+ # AUTO-REPAIR: try platform-appropriate repair
446
+ repair_ok=false
447
+ if $IS_MACOS; then
448
+ try_repair_launchagent "$plist_id" "$proc_grep" && repair_ok=true
449
+ else
450
+ try_repair_systemd "$plist_id" && repair_ok=true
451
+ fi
452
+ if $repair_ok; then
391
453
  loaded="yes"
392
454
  status="HEALED"
393
- details="${details}Self-healed: bootstrapped. "
455
+ details="${details}Self-healed: service re-registered. "
394
456
  TOTAL_HEALED=$((TOTAL_HEALED + 1))
395
457
  else
396
458
  status="FAIL"
397
- details="${details}Not loaded in launchctl (repair failed). "
459
+ details="${details}Service not loaded (repair failed). "
398
460
  fi
399
461
  fi
400
462
 
@@ -404,9 +466,10 @@ for monitor in "${MONITORS[@]}"; do
404
466
  proc_alive="yes"
405
467
  else
406
468
  proc_alive="no"
407
- # AUTO-REPAIR: try to kickstart
469
+ # AUTO-REPAIR: try to kickstart (platform-appropriate)
408
470
  if [ "$status" != "FAIL" ] && [ "$status" != "HEALED" ]; then
409
- if try_repair_launchagent "$plist_id" "$proc_grep"; then
471
+ if ($IS_MACOS && try_repair_launchagent "$plist_id" "$proc_grep") || \
472
+ (! $IS_MACOS && try_repair_systemd "$plist_id"); then
410
473
  proc_alive="yes"
411
474
  status="HEALED"
412
475
  details="${details}Self-healed: kickstarted. "
@@ -813,7 +876,7 @@ if [ "$TOTAL_FAIL" -gt 0 ]; then
813
876
  Schedule: ${m_sched}
814
877
  Type: ${m_type}
815
878
  Failure reason: ${m_details}
816
- Plist: ~/Library/LaunchAgents/${m_plist}.plist
879
+ Service config: $($IS_MACOS && echo "~/Library/LaunchAgents/${m_plist}.plist" || echo "~/.config/systemd/user/${m_plist}")
817
880
  Process grep: ${m_proc}
818
881
  Stderr (last 20 lines):
819
882
  ${STDERR_TAIL}
@@ -828,12 +891,12 @@ ${STDOUT_TAIL}
828
891
  log "Launching NEXO Level 2 repair..."
829
892
 
830
893
  # Build propagation instructions if core services failed
831
- # Only the origin maintainer (user) propagates fixes to the public repo
894
+ # Only runs when NEXO_MAINTAINER=1 and NEXO_PUBLIC_REPO is configured
832
895
  PROPAGATE_BLOCK=""
833
- # If you maintain a public NEXO repo, configure propagation here
834
- NEXO_PUBLIC_REPO="${NEXO_PUBLIC_REPO:-}"
835
- if $HAS_CORE_FAILS && [ -n "$NEXO_PUBLIC_REPO" ] && [ -d "$NEXO_PUBLIC_REPO/.git" ]; then
836
- PROPAGATE_BLOCK="
896
+ if [ "${NEXO_MAINTAINER:-}" = "1" ]; then
897
+ NEXO_PUBLIC_REPO="${NEXO_PUBLIC_REPO:-}"
898
+ if $HAS_CORE_FAILS && [ -n "$NEXO_PUBLIC_REPO" ] && [ -d "$NEXO_PUBLIC_REPO/.git" ]; then
899
+ PROPAGATE_BLOCK="
837
900
  PROPAGATION (for [core] fixes ONLY):
838
901
  If your fix modifies a file under $NEXO_HOME/ (server.py, db/, plugins/, scripts/):
839
902
  1. Commit the fix locally with a descriptive message
@@ -842,6 +905,7 @@ If your fix modifies a file under $NEXO_HOME/ (server.py, db/, plugins/, scripts
842
905
  4. Commit + push
843
906
  5. Create a GitHub release with gh release create
844
907
  Do NOT propagate fixes for [personal] services — those stay local only."
908
+ fi
845
909
  fi
846
910
 
847
911
  # Write prompt to temp file (avoids heredoc quoting issues in subshell)
@@ -849,7 +913,7 @@ Do NOT propagate fixes for [personal] services — those stay local only."
849
913
  cat > "$REPAIR_PROMPT_FILE" <<NEXOPROMPT
850
914
  WATCHDOG LEVEL 2 REPAIR — Automated diagnostic session.
851
915
 
852
- The watchdog detected services that failed mechanical repair (launchctl bootstrap/kickstart).
916
+ The watchdog detected services that failed mechanical repair (launchctl/systemctl re-registration).
853
917
  Your job: diagnose the root cause and fix it. Do NOT ask the user anything — resolve autonomously.
854
918
 
855
919
  Each failure is tagged [core] or [personal]:
@@ -860,10 +924,10 @@ FAILURES:
860
924
  ${FAIL_DETAILS}
861
925
 
862
926
  STEPS:
863
- 1. Read the plist file to understand the service configuration
927
+ 1. Read the service config (plist on macOS, systemd unit on Linux) to understand the service
864
928
  2. Check stderr/stdout logs for the actual error
865
929
  3. Fix the root cause (missing file, bad config, dependency issue, etc.)
866
- 4. Reload the service and verify it is running
930
+ 4. Reload the service and verify it is running (launchctl on macOS, systemctl on Linux)
867
931
  5. Log what you did to $NEXO_HOME/logs/watchdog-repair-result.log
868
932
  ${PROPAGATE_BLOCK}
869
933
 
package/src/server.py CHANGED
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  """NEXO MCP Server — Phase 4: Hot-Reload Plugin System."""
2
3
 
3
4
  import os
@@ -54,7 +55,71 @@ def _server_init():
54
55
  with open(_pid_file, "w") as f:
55
56
  f.write(str(os.getpid()))
56
57
 
57
- init_db()
58
+ # ── Database initialization with recovery ─────────────────────
59
+ import sqlite3
60
+ try:
61
+ init_db()
62
+ except sqlite3.DatabaseError as exc:
63
+ # Corruption or unreadable DB — attempt restore from backup
64
+ print(f"[NEXO] DB init failed: {exc}", file=sys.stderr)
65
+ _recovered = False
66
+ try:
67
+ from db._core import DB_PATH as _db_path
68
+ import glob as _glob
69
+ _backup_dir = os.path.join(
70
+ os.environ.get("NEXO_HOME", os.path.join(os.path.expanduser("~"), ".nexo")),
71
+ "backups",
72
+ )
73
+ _backups = sorted(_glob.glob(os.path.join(_backup_dir, "nexo-*.db")), reverse=True)
74
+ for _bk in _backups:
75
+ try:
76
+ _test = sqlite3.connect(_bk)
77
+ _result = _test.execute("PRAGMA integrity_check").fetchone()
78
+ _test.close()
79
+ if _result and _result[0] == "ok":
80
+ # Valid backup found — replace corrupt DB
81
+ import shutil
82
+ # Close any open connection before replacing
83
+ try:
84
+ close_db()
85
+ except Exception:
86
+ pass
87
+ shutil.copy2(_bk, _db_path)
88
+ print(f"[NEXO] Restored DB from backup: {os.path.basename(_bk)}", file=sys.stderr)
89
+ init_db()
90
+ _recovered = True
91
+ break
92
+ except Exception:
93
+ continue
94
+ except Exception as restore_exc:
95
+ print(f"[NEXO] Backup restore failed: {restore_exc}", file=sys.stderr)
96
+
97
+ if not _recovered:
98
+ # No valid backup — nuke corrupt file and start fresh
99
+ try:
100
+ close_db()
101
+ except Exception:
102
+ pass
103
+ try:
104
+ from db._core import DB_PATH as _db_path
105
+ if os.path.exists(_db_path):
106
+ _corrupt_path = _db_path + ".corrupt"
107
+ os.rename(_db_path, _corrupt_path)
108
+ print(f"[NEXO] Corrupt DB moved to {os.path.basename(_corrupt_path)}", file=sys.stderr)
109
+ # Remove WAL/SHM files too
110
+ for _ext in (".db-wal", ".db-shm"):
111
+ _wal = _db_path.replace(".db", _ext)
112
+ if os.path.exists(_wal):
113
+ os.remove(_wal)
114
+ except Exception:
115
+ pass
116
+ try:
117
+ init_db()
118
+ print("[NEXO] Fresh database created.", file=sys.stderr)
119
+ except Exception as fresh_exc:
120
+ print(f"[NEXO] FATAL: Cannot initialize database: {fresh_exc}", file=sys.stderr)
121
+ print("[NEXO] Check permissions on NEXO_HOME/data/ and disk space.", file=sys.stderr)
122
+ sys.exit(1)
58
123
 
59
124
  # ── Auto-update check (non-blocking, max 5s) ──────────────────
60
125
  try:
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  """Coordination tools: file tracking, messaging, Q&A."""
2
3
 
3
4
  from db import (
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  """Session management tools: startup, heartbeat, status."""
2
3
 
3
4
  import time