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
|
@@ -48,7 +48,7 @@ from datetime import date
|
|
|
48
48
|
today_str = '$TODAY'
|
|
49
49
|
weekday = '$WEEKDAY'
|
|
50
50
|
nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
|
|
51
|
-
db_path = os.path.join(nexo_home, 'nexo.db')
|
|
51
|
+
db_path = os.path.join(nexo_home, 'data', 'nexo.db')
|
|
52
52
|
|
|
53
53
|
lines = []
|
|
54
54
|
lines.append(f'## Date: {today_str} ({weekday})')
|
|
@@ -212,7 +212,11 @@ except Exception as e:
|
|
|
212
212
|
fi
|
|
213
213
|
|
|
214
214
|
# ─── Cortex Report: what happened while user was away ───
|
|
215
|
-
|
|
215
|
+
# Check brain/ (canonical) first, fall back to cortex/ (legacy)
|
|
216
|
+
CORTEX_BRIEFING="$NEXO_HOME/brain/last-briefing.json"
|
|
217
|
+
if [ ! -f "$CORTEX_BRIEFING" ] && [ -f "$NEXO_HOME/cortex/last-briefing.json" ]; then
|
|
218
|
+
CORTEX_BRIEFING="$NEXO_HOME/cortex/last-briefing.json"
|
|
219
|
+
fi
|
|
216
220
|
if [ -f "$CORTEX_BRIEFING" ]; then
|
|
217
221
|
CORTEX_SECTION=$(python3 -c "
|
|
218
222
|
import json
|
|
@@ -25,7 +25,8 @@ TOOL_LOG="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
|
|
|
25
25
|
python3 -c "
|
|
26
26
|
import sys, json, os
|
|
27
27
|
nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
|
|
28
|
-
|
|
28
|
+
nexo_code = os.environ.get('NEXO_CODE', nexo_home)
|
|
29
|
+
sys.path.insert(0, nexo_code)
|
|
29
30
|
os.environ['NEXO_SKIP_FS_INDEX'] = '1'
|
|
30
31
|
from db import init_db, get_db, get_active_sessions, upsert_diary_draft, get_diary_draft
|
|
31
32
|
init_db()
|
package/src/kg_populate.py
CHANGED
|
@@ -13,7 +13,10 @@ from db import get_db
|
|
|
13
13
|
|
|
14
14
|
def _cognitive_db():
|
|
15
15
|
"""Direct cognitive.db connection (for somatic_markers)."""
|
|
16
|
-
|
|
16
|
+
nexo_home = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
17
|
+
data_dir = os.path.join(nexo_home, "data")
|
|
18
|
+
os.makedirs(data_dir, exist_ok=True)
|
|
19
|
+
path = os.path.join(data_dir, "cognitive.db")
|
|
17
20
|
conn = sqlite3.connect(path)
|
|
18
21
|
conn.row_factory = sqlite3.Row
|
|
19
22
|
return conn
|
|
@@ -15,7 +15,10 @@ import sys
|
|
|
15
15
|
import time
|
|
16
16
|
import numpy as np
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
19
|
+
_data_dir = os.path.join(NEXO_HOME, "data")
|
|
20
|
+
os.makedirs(_data_dir, exist_ok=True)
|
|
21
|
+
DB_PATH = os.path.join(_data_dir, "cognitive.db")
|
|
19
22
|
BACKUP_PATH = DB_PATH + ".bak-384dims-pre-upgrade"
|
|
20
23
|
|
|
21
24
|
MODELS = {
|
package/src/plugin_loader.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Dynamic plugin loader for NEXO MCP server."""
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
|
+
import importlib.util
|
|
4
5
|
import os
|
|
5
6
|
import signal
|
|
6
7
|
import sys
|
|
@@ -9,7 +10,12 @@ import time
|
|
|
9
10
|
from db import get_db
|
|
10
11
|
from fastmcp.tools import Tool
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
SERVER_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
14
|
+
PLUGINS_DIR = os.path.join(SERVER_DIR, "plugins")
|
|
15
|
+
|
|
16
|
+
# Personal plugins directory: NEXO_HOME/plugins/ (env var, defaults to ~/.nexo/)
|
|
17
|
+
NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
18
|
+
PERSONAL_PLUGINS_DIR = os.path.join(NEXO_HOME, "plugins")
|
|
13
19
|
|
|
14
20
|
PLUGIN_LOAD_TIMEOUT = 10 # seconds per plugin
|
|
15
21
|
|
|
@@ -22,41 +28,95 @@ def _timeout_handler(signum, frame):
|
|
|
22
28
|
raise _PluginTimeout("Plugin loading timed out")
|
|
23
29
|
|
|
24
30
|
|
|
31
|
+
def _ensure_src_in_path():
|
|
32
|
+
"""Ensure server src/ is in sys.path so personal plugins can import db, cognitive, etc."""
|
|
33
|
+
if SERVER_DIR not in sys.path:
|
|
34
|
+
sys.path.insert(0, SERVER_DIR)
|
|
35
|
+
|
|
36
|
+
|
|
25
37
|
def load_all_plugins(mcp) -> int:
|
|
26
|
-
"""Load all plugins from
|
|
27
|
-
|
|
28
|
-
return 0
|
|
38
|
+
"""Load all plugins from repo and personal directories at startup. Returns total tools loaded."""
|
|
39
|
+
_ensure_src_in_path()
|
|
29
40
|
total = 0
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
|
|
42
|
+
# Collect plugins: repo first, personal overrides
|
|
43
|
+
plugin_map = {} # filename -> (dir_path, source_label)
|
|
44
|
+
|
|
45
|
+
# 1. Repo plugins (base)
|
|
46
|
+
if os.path.isdir(PLUGINS_DIR):
|
|
47
|
+
for f in sorted(os.listdir(PLUGINS_DIR)):
|
|
48
|
+
if f.endswith(".py") and f != "__init__.py":
|
|
49
|
+
plugin_map[f] = (PLUGINS_DIR, "repo")
|
|
50
|
+
|
|
51
|
+
# 2. Personal plugins (override if same filename)
|
|
52
|
+
if os.path.isdir(PERSONAL_PLUGINS_DIR):
|
|
53
|
+
for f in sorted(os.listdir(PERSONAL_PLUGINS_DIR)):
|
|
54
|
+
if f.endswith(".py") and f != "__init__.py":
|
|
55
|
+
source = "personal (override)" if f in plugin_map else "personal"
|
|
56
|
+
plugin_map[f] = (PERSONAL_PLUGINS_DIR, source)
|
|
57
|
+
|
|
58
|
+
# Load all in sorted order
|
|
59
|
+
for f in sorted(plugin_map):
|
|
60
|
+
plugins_dir, source_label = plugin_map[f]
|
|
61
|
+
try:
|
|
62
|
+
old_handler = signal.signal(signal.SIGALRM, _timeout_handler)
|
|
63
|
+
signal.alarm(PLUGIN_LOAD_TIMEOUT)
|
|
32
64
|
try:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
except Exception as e:
|
|
44
|
-
print(f"[PLUGIN ERROR] {f}: {e}", file=sys.stderr)
|
|
65
|
+
n = load_plugin(mcp, f, plugins_dir=plugins_dir)
|
|
66
|
+
total += n
|
|
67
|
+
print(f"[PLUGIN LOADED] {f} ({n} tools) from {source_label}: {plugins_dir}", file=sys.stderr)
|
|
68
|
+
finally:
|
|
69
|
+
signal.alarm(0)
|
|
70
|
+
signal.signal(signal.SIGALRM, old_handler)
|
|
71
|
+
except _PluginTimeout:
|
|
72
|
+
print(f"[PLUGIN TIMEOUT] {f}: skipped after {PLUGIN_LOAD_TIMEOUT}s", file=sys.stderr)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"[PLUGIN ERROR] {f}: {e}", file=sys.stderr)
|
|
45
75
|
return total
|
|
46
76
|
|
|
47
77
|
|
|
48
|
-
def load_plugin(mcp, filename: str) -> int:
|
|
49
|
-
"""Load or reload a single plugin. Returns number of tools registered.
|
|
78
|
+
def load_plugin(mcp, filename: str, plugins_dir: str | None = None) -> int:
|
|
79
|
+
"""Load or reload a single plugin. Returns number of tools registered.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
plugins_dir: Directory to load from. If None, searches repo PLUGINS_DIR first,
|
|
83
|
+
then PERSONAL_PLUGINS_DIR. Personal plugins are loaded via
|
|
84
|
+
importlib.util.spec_from_file_location.
|
|
85
|
+
"""
|
|
50
86
|
if not filename.endswith(".py"):
|
|
51
87
|
filename += ".py"
|
|
52
88
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
89
|
+
if plugins_dir is not None:
|
|
90
|
+
filepath = os.path.join(plugins_dir, filename)
|
|
91
|
+
if not os.path.isfile(filepath):
|
|
92
|
+
raise FileNotFoundError(f"Plugin not found: {filepath}")
|
|
93
|
+
else:
|
|
94
|
+
# Search repo first, then personal
|
|
95
|
+
repo_path = os.path.join(PLUGINS_DIR, filename)
|
|
96
|
+
personal_path = os.path.join(PERSONAL_PLUGINS_DIR, filename)
|
|
97
|
+
if os.path.isfile(repo_path):
|
|
98
|
+
plugins_dir = PLUGINS_DIR
|
|
99
|
+
filepath = repo_path
|
|
100
|
+
elif os.path.isfile(personal_path):
|
|
101
|
+
plugins_dir = PERSONAL_PLUGINS_DIR
|
|
102
|
+
filepath = personal_path
|
|
103
|
+
else:
|
|
104
|
+
raise FileNotFoundError(
|
|
105
|
+
f"Plugin not found in repo ({PLUGINS_DIR}) or personal ({PERSONAL_PLUGINS_DIR}): {filename}"
|
|
106
|
+
)
|
|
56
107
|
|
|
57
108
|
module_name = f"plugins.{filename[:-3]}"
|
|
58
109
|
|
|
59
|
-
|
|
110
|
+
# For personal plugins (outside repo), use spec_from_file_location
|
|
111
|
+
if plugins_dir != PLUGINS_DIR:
|
|
112
|
+
_ensure_src_in_path()
|
|
113
|
+
spec = importlib.util.spec_from_file_location(module_name, filepath)
|
|
114
|
+
if spec is None or spec.loader is None:
|
|
115
|
+
raise ImportError(f"Cannot create module spec for {filepath}")
|
|
116
|
+
mod = importlib.util.module_from_spec(spec)
|
|
117
|
+
sys.modules[module_name] = mod
|
|
118
|
+
spec.loader.exec_module(mod)
|
|
119
|
+
elif module_name in sys.modules:
|
|
60
120
|
mod = importlib.reload(sys.modules[module_name])
|
|
61
121
|
else:
|
|
62
122
|
mod = importlib.import_module(module_name)
|
|
@@ -73,13 +133,18 @@ def load_plugin(mcp, filename: str) -> int:
|
|
|
73
133
|
mcp.add_tool(t)
|
|
74
134
|
tool_names.append(name)
|
|
75
135
|
|
|
76
|
-
|
|
136
|
+
source_label = "personal" if plugins_dir != PLUGINS_DIR else "repo"
|
|
137
|
+
_update_registry(filename, len(tool_names), ",".join(tool_names), source_label)
|
|
77
138
|
|
|
78
139
|
return len(tool_names)
|
|
79
140
|
|
|
80
141
|
|
|
81
142
|
def remove_plugin(mcp, filename: str) -> list[str]:
|
|
82
|
-
"""
|
|
143
|
+
"""Unregister a plugin's tools from MCP and clean the registry.
|
|
144
|
+
|
|
145
|
+
Does NOT delete plugin files — only unregisters tools to avoid
|
|
146
|
+
accidental deletion of code from repo or personal directories.
|
|
147
|
+
"""
|
|
83
148
|
if not filename.endswith(".py"):
|
|
84
149
|
filename += ".py"
|
|
85
150
|
|
|
@@ -100,10 +165,6 @@ def remove_plugin(mcp, filename: str) -> list[str]:
|
|
|
100
165
|
module_name = f"plugins.{filename[:-3]}"
|
|
101
166
|
sys.modules.pop(module_name, None)
|
|
102
167
|
|
|
103
|
-
filepath = os.path.join(PLUGINS_DIR, filename)
|
|
104
|
-
if os.path.isfile(filepath):
|
|
105
|
-
os.remove(filepath)
|
|
106
|
-
|
|
107
168
|
conn = get_db()
|
|
108
169
|
conn.execute("DELETE FROM plugins WHERE filename = ?", (filename,))
|
|
109
170
|
conn.commit()
|
|
@@ -112,12 +173,17 @@ def remove_plugin(mcp, filename: str) -> list[str]:
|
|
|
112
173
|
|
|
113
174
|
|
|
114
175
|
def list_plugins() -> list[dict]:
|
|
115
|
-
"""List all registered plugins."""
|
|
176
|
+
"""List all registered plugins with source info (repo/personal)."""
|
|
116
177
|
conn = get_db()
|
|
117
178
|
rows = conn.execute(
|
|
118
179
|
"SELECT filename, tools_count, tool_names, loaded_at, created_by FROM plugins ORDER BY filename"
|
|
119
180
|
).fetchall()
|
|
120
|
-
|
|
181
|
+
result = []
|
|
182
|
+
for r in rows:
|
|
183
|
+
d = dict(r)
|
|
184
|
+
d["source"] = d.get("created_by", "repo")
|
|
185
|
+
result.append(d)
|
|
186
|
+
return result
|
|
121
187
|
|
|
122
188
|
|
|
123
189
|
def _update_registry(filename: str, tools_count: int, tool_names: str, created_by: str):
|
|
@@ -128,8 +194,8 @@ def _update_registry(filename: str, tools_count: int, tool_names: str, created_b
|
|
|
128
194
|
conn.execute(
|
|
129
195
|
"INSERT INTO plugins (filename, tools_count, tool_names, loaded_at, created_by) "
|
|
130
196
|
"VALUES (?, ?, ?, ?, ?) "
|
|
131
|
-
"ON CONFLICT(filename) DO UPDATE SET tools_count=?, tool_names=?, loaded_at=?",
|
|
132
|
-
(filename, tools_count, tool_names, now, created_by, tools_count, tool_names, now),
|
|
197
|
+
"ON CONFLICT(filename) DO UPDATE SET tools_count=?, tool_names=?, loaded_at=?, created_by=?",
|
|
198
|
+
(filename, tools_count, tool_names, now, created_by, tools_count, tool_names, now, created_by),
|
|
133
199
|
)
|
|
134
200
|
conn.commit()
|
|
135
201
|
except Exception as e:
|
|
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
|
package/src/plugins/agents.py
CHANGED
|
@@ -15,7 +15,7 @@ def handle_agent_create(id: str, name: str, specialization: str, model: str = "s
|
|
|
15
15
|
tools: str = "", context_files: str = "", rules: str = "") -> str:
|
|
16
16
|
"""Register a new agent in the registry."""
|
|
17
17
|
create_agent(id, name, specialization, model, tools, context_files, rules)
|
|
18
|
-
return f"
|
|
18
|
+
return f"Agent '{id}' ({name}) registered. Model: {model}"
|
|
19
19
|
|
|
20
20
|
def handle_agent_update(id: str, name: str = "", specialization: str = "", model: str = "",
|
|
21
21
|
tools: str = "", context_files: str = "", rules: str = "") -> str:
|
|
@@ -32,7 +32,7 @@ def handle_agent_list() -> str:
|
|
|
32
32
|
"""List all registered agents."""
|
|
33
33
|
agents = list_agents()
|
|
34
34
|
if not agents: return "No agents registered."
|
|
35
|
-
lines = ["
|
|
35
|
+
lines = ["REGISTERED AGENTS:"]
|
|
36
36
|
for a in agents:
|
|
37
37
|
lines.append(f" {a['id']} — {a['name']} ({a['model']}) — {a['specialization'][:60]}")
|
|
38
38
|
return "\n".join(lines)
|
package/src/plugins/backup.py
CHANGED
|
@@ -5,8 +5,9 @@ import time
|
|
|
5
5
|
import glob
|
|
6
6
|
from db import get_db
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
9
|
+
DB_PATH = os.path.join(NEXO_HOME, "data", "nexo.db")
|
|
10
|
+
BACKUP_DIR = os.path.join(NEXO_HOME, "backups")
|
|
10
11
|
|
|
11
12
|
RETENTION_DAYS = 7
|
|
12
13
|
|
|
@@ -33,10 +34,10 @@ def handle_backup_now() -> str:
|
|
|
33
34
|
def handle_backup_list() -> str:
|
|
34
35
|
"""List available backups with dates and sizes."""
|
|
35
36
|
if not os.path.isdir(BACKUP_DIR):
|
|
36
|
-
return "
|
|
37
|
+
return "No backups."
|
|
37
38
|
files = sorted(glob.glob(os.path.join(BACKUP_DIR, "nexo-*.db")), reverse=True)
|
|
38
39
|
if not files:
|
|
39
|
-
return "
|
|
40
|
+
return "No backups."
|
|
40
41
|
lines = [f"BACKUPS ({len(files)}):"]
|
|
41
42
|
total_size = 0
|
|
42
43
|
for f in files:
|
|
@@ -12,7 +12,11 @@ def _get_db():
|
|
|
12
12
|
def _seed_if_empty():
|
|
13
13
|
"""Seed rules from JSON if table is empty (first run after migration)."""
|
|
14
14
|
conn = _get_db()
|
|
15
|
-
|
|
15
|
+
try:
|
|
16
|
+
count = conn.execute("SELECT COUNT(*) FROM core_rules WHERE is_active = 1").fetchone()[0]
|
|
17
|
+
except Exception:
|
|
18
|
+
# Table doesn't exist yet — migrations haven't run. Bail gracefully.
|
|
19
|
+
return
|
|
16
20
|
if count > 0:
|
|
17
21
|
return
|
|
18
22
|
|
|
@@ -39,7 +39,7 @@ def handle_decision_log(domain: str, decision: str, alternatives: str = '',
|
|
|
39
39
|
if domain not in valid_domains:
|
|
40
40
|
return f"ERROR: domain must be one of: {', '.join(sorted(valid_domains))}"
|
|
41
41
|
if confidence not in ('high', 'medium', 'low'):
|
|
42
|
-
return f"ERROR: confidence
|
|
42
|
+
return f"ERROR: confidence must be high, medium, or low"
|
|
43
43
|
|
|
44
44
|
sid = session_id or 'unknown'
|
|
45
45
|
result = log_decision(sid, domain, decision, alternatives, based_on, confidence, context_ref)
|
|
@@ -84,7 +84,7 @@ def handle_decision_outcome(id: int, outcome: str) -> str:
|
|
|
84
84
|
(id,)
|
|
85
85
|
)
|
|
86
86
|
conn.commit()
|
|
87
|
-
return f"Decision #{id} outcome
|
|
87
|
+
return f"Decision #{id} outcome recorded: {outcome[:100]}"
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
def handle_decision_search(query: str = '', domain: str = '', days: int = 30) -> str:
|
|
@@ -297,7 +297,7 @@ def handle_change_log(files: str, what_changed: str, why: str,
|
|
|
297
297
|
session_id: Current session ID
|
|
298
298
|
"""
|
|
299
299
|
if not files or not what_changed or not why:
|
|
300
|
-
return "ERROR: files, what_changed,
|
|
300
|
+
return "ERROR: files, what_changed, and why are required"
|
|
301
301
|
sid = session_id or 'unknown'
|
|
302
302
|
result = log_change(sid, files, what_changed, why, triggered_by, affects, risks, verify, commit_ref)
|
|
303
303
|
if "error" in result:
|
|
@@ -313,7 +313,7 @@ def handle_change_log(files: str, what_changed: str, why: str,
|
|
|
313
313
|
on_change_log(change_id, files, "")
|
|
314
314
|
except Exception:
|
|
315
315
|
pass
|
|
316
|
-
msg = f"Change #{change_id}
|
|
316
|
+
msg = f"Change #{change_id} recorded: {files[:60]} — {what_changed[:60]}"
|
|
317
317
|
if not commit_ref:
|
|
318
318
|
msg += f"\n⚠ NO COMMIT. Use nexo_change_commit({change_id}, 'hash') after push, or 'server-direct' if it was a direct server edit."
|
|
319
319
|
return msg
|
|
@@ -351,6 +351,9 @@ def handle_change_search(query: str = '', files: str = '', days: int = 30) -> st
|
|
|
351
351
|
def handle_change_commit(id: int, commit_ref: str) -> str:
|
|
352
352
|
"""Link a change log entry to its git commit hash after committing.
|
|
353
353
|
|
|
354
|
+
After linking, automatically resolves any open followups that match
|
|
355
|
+
the change (by file overlap, keyword similarity, or explicit ID reference).
|
|
356
|
+
|
|
354
357
|
Args:
|
|
355
358
|
id: Change log entry ID
|
|
356
359
|
commit_ref: Git commit hash
|
|
@@ -358,7 +361,13 @@ def handle_change_commit(id: int, commit_ref: str) -> str:
|
|
|
358
361
|
result = update_change_commit(id, commit_ref)
|
|
359
362
|
if "error" in result:
|
|
360
363
|
return f"ERROR: {result['error']}"
|
|
361
|
-
|
|
364
|
+
|
|
365
|
+
msg = f"Change #{id} vinculado a commit {commit_ref[:8]}"
|
|
366
|
+
auto_resolved = result.get("_auto_resolved", [])
|
|
367
|
+
if auto_resolved:
|
|
368
|
+
ids = ", ".join(auto_resolved)
|
|
369
|
+
msg += f"\n✅ AUTO-RESOLVED followups: {ids}"
|
|
370
|
+
return msg
|
|
362
371
|
|
|
363
372
|
|
|
364
373
|
def handle_recall(query: str, days: int = 30) -> str:
|
package/src/plugins/evolution.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Evolution plugin — NEXO self-improvement tools for interactive sessions."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from db import get_latest_metrics, get_evolution_history, update_evolution_log_status, get_db
|
|
4
5
|
|
|
5
6
|
|
|
@@ -59,9 +60,12 @@ def handle_evolution_propose() -> str:
|
|
|
59
60
|
import json
|
|
60
61
|
from pathlib import Path
|
|
61
62
|
nexo_home = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
62
|
-
|
|
63
|
+
# Check brain/ (canonical) first, fall back to cortex/ (legacy)
|
|
64
|
+
obj_file = nexo_home / "brain" / "evolution-objective.json"
|
|
63
65
|
if not obj_file.exists():
|
|
64
|
-
|
|
66
|
+
obj_file = nexo_home / "cortex" / "evolution-objective.json"
|
|
67
|
+
if not obj_file.exists():
|
|
68
|
+
return "ERROR: evolution-objective.json not found. Run the installer or create one in ~/.nexo/brain/"
|
|
65
69
|
try:
|
|
66
70
|
obj = json.loads(obj_file.read_text())
|
|
67
71
|
if not obj.get("evolution_enabled", True):
|
package/src/plugins/guard.py
CHANGED
|
@@ -110,16 +110,22 @@ def handle_guard_check(files: str = "", area: str = "", include_schemas: str = "
|
|
|
110
110
|
w = r["weight"] or 0.5
|
|
111
111
|
result["learnings"].append({"id": r["id"], "category": r["category"], "rule": r["title"], "priority": pri, "weight": w})
|
|
112
112
|
|
|
113
|
-
# 3. Universal rules
|
|
113
|
+
# 3. Universal rules — only from matching area or nexo-ops (not ALL learnings)
|
|
114
|
+
universal_categories = {"nexo-ops"}
|
|
115
|
+
if area:
|
|
116
|
+
universal_categories.add(area)
|
|
117
|
+
placeholders = ",".join("?" for _ in universal_categories)
|
|
114
118
|
rows = conn.execute(
|
|
115
|
-
"SELECT id, category, title, content FROM learnings WHERE "
|
|
116
|
-
"
|
|
117
|
-
"OR content LIKE '%
|
|
119
|
+
f"SELECT id, category, title, content, priority FROM learnings WHERE "
|
|
120
|
+
f"category IN ({placeholders}) AND ("
|
|
121
|
+
f"content LIKE '%SIEMPRE%' OR content LIKE '%NUNCA%' OR content LIKE '%ANTES%' "
|
|
122
|
+
f"OR content LIKE '%always%' OR content LIKE '%never%')",
|
|
123
|
+
tuple(universal_categories)
|
|
118
124
|
).fetchall()
|
|
119
125
|
for r in rows:
|
|
120
126
|
if r["id"] not in seen_ids:
|
|
121
127
|
seen_ids.add(r["id"])
|
|
122
|
-
result["universal_rules"].append({"id": r["id"], "rule": r["title"], "category": r["category"]})
|
|
128
|
+
result["universal_rules"].append({"id": r["id"], "rule": r["title"], "category": r["category"], "priority": r["priority"] or "medium"})
|
|
123
129
|
|
|
124
130
|
# 4. DB schemas if files contain SQL keywords
|
|
125
131
|
if include_schemas_bool and file_list:
|
|
@@ -173,8 +179,9 @@ def handle_guard_check(files: str = "", area: str = "", include_schemas: str = "
|
|
|
173
179
|
})
|
|
174
180
|
continue
|
|
175
181
|
|
|
176
|
-
# Path (b):
|
|
177
|
-
|
|
182
|
+
# Path (b): Only promote to blocking if high/critical priority AND title has prohibition keyword
|
|
183
|
+
pri = learning.get("priority", "medium")
|
|
184
|
+
if pri in ("critical", "high") and BLOCKING_KEYWORDS.search(learning["rule"]):
|
|
178
185
|
blocking_seen.add(lid)
|
|
179
186
|
result["blocking_rules"].append({
|
|
180
187
|
"id": lid, "rule": learning["rule"], "repetitions": rep_count,
|
|
@@ -297,8 +304,9 @@ def handle_guard_check(files: str = "", area: str = "", include_schemas: str = "
|
|
|
297
304
|
lines.append("")
|
|
298
305
|
|
|
299
306
|
if result["learnings"]:
|
|
300
|
-
|
|
301
|
-
|
|
307
|
+
shown = result["learnings"][:10] # Cap at 10, not 15
|
|
308
|
+
lines.append(f"RELEVANT LEARNINGS ({len(result['learnings'])}, showing {len(shown)}):")
|
|
309
|
+
for l in shown:
|
|
302
310
|
lines.append(f" #{l['id']} [{l['category']}] {l['rule']}")
|
|
303
311
|
lines.append("")
|
|
304
312
|
|
|
@@ -310,8 +318,9 @@ def handle_guard_check(files: str = "", area: str = "", include_schemas: str = "
|
|
|
310
318
|
lines.append("")
|
|
311
319
|
|
|
312
320
|
if result["universal_rules"]:
|
|
313
|
-
|
|
314
|
-
|
|
321
|
+
shown_u = result["universal_rules"][:5] # Cap at 5
|
|
322
|
+
lines.append(f"UNIVERSAL RULES ({len(result['universal_rules'])}, showing {len(shown_u)}):")
|
|
323
|
+
for r in shown_u:
|
|
315
324
|
lines.append(f" #{r['id']} {r['rule']}")
|
|
316
325
|
lines.append("")
|
|
317
326
|
|