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.
- package/README.md +1 -1
- package/bin/nexo-brain.js +92 -9
- package/bin/postinstall.js +22 -15
- package/package.json +7 -4
- package/src/auto_update.py +194 -5
- package/src/crons/sync.py +6 -2
- package/src/db/_core.py +1 -0
- package/src/db/_entities.py +1 -0
- package/src/db/_episodic.py +1 -0
- package/src/db/_learnings.py +1 -0
- package/src/db/_reminders.py +1 -0
- package/src/db/_schema.py +11 -1
- package/src/db/_sessions.py +1 -0
- package/src/db/_skills.py +1 -0
- package/src/hooks/capture-tool-logs.sh +23 -6
- package/src/hooks/session-start.sh +4 -3
- package/src/plugin_loader.py +1 -0
- package/src/plugins/update.py +377 -26
- package/src/scripts/deep-sleep/apply_findings.py +1 -0
- package/src/scripts/deep-sleep/collect.py +1 -0
- package/src/scripts/deep-sleep/extract.py +1 -0
- package/src/scripts/deep-sleep/synthesize.py +1 -0
- package/src/scripts/nexo-catchup.py +29 -4
- package/src/scripts/nexo-daily-self-audit.py +21 -1
- package/src/scripts/nexo-evolution-run.py +21 -1
- package/src/scripts/nexo-learning-housekeep.py +1 -0
- package/src/scripts/nexo-postmortem-consolidator.py +34 -9
- package/src/scripts/nexo-sleep.py +32 -10
- package/src/scripts/nexo-synthesis.py +29 -9
- package/src/scripts/nexo-update.sh +109 -7
- package/src/scripts/nexo-watchdog.sh +122 -58
- package/src/server.py +66 -1
- package/src/tools_coordination.py +1 -0
- package/src/tools_sessions.py +1 -0
- package/scripts/migrate-to-unified 2.sh +0 -813
- package/scripts/migrate-to-unified.sh +0 -813
- package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
- package/scripts/migrate-v1.5-to-v1.6.py +0 -778
- package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
- package/scripts/migrate-v1.7-to-v1.8.py +0 -214
- package/scripts/nexo-preflight.sh +0 -236
- package/scripts/pre-commit-check 2.sh +0 -55
- package/scripts/pre-commit-check.sh +0 -55
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.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__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.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_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_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions 2.py +0 -159
- package/src/auto_update 2.py +0 -634
- package/src/claim_graph 2.py +0 -323
- package/src/cognitive/__init__ 2.py +0 -62
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-312.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-312.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-312.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-312.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-312.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-312.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-312.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core 2.py +0 -567
- package/src/cognitive/_decay 2.py +0 -382
- package/src/cognitive/_ingest 2.py +0 -892
- package/src/cognitive/_memory 2.py +0 -912
- package/src/cognitive/_search 2.py +0 -949
- package/src/cognitive/_trust 2.py +0 -464
- package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
- package/src/crons/manifest 2.json +0 -106
- package/src/crons/sync 2.py +0 -217
- package/src/dashboard/__init__ 2.py +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app 2.py +0 -789
- package/src/db/__init__ 2.py +0 -89
- 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__/_cron_runs.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.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__/_skills.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_skills.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 2.py +0 -417
- package/src/db/_credentials 2.py +0 -124
- package/src/db/_entities 2.py +0 -178
- package/src/db/_episodic 2.py +0 -738
- package/src/db/_evolution 2.py +0 -54
- package/src/db/_fts 2.py +0 -406
- package/src/db/_learnings 2.py +0 -168
- package/src/db/_reminders 2.py +0 -338
- package/src/db/_schema 2.py +0 -364
- package/src/db/_sessions 2.py +0 -300
- package/src/db/_tasks 2.py +0 -91
- package/src/evolution_cycle 2.py +0 -266
- package/src/hnsw_index 2.py +0 -254
- package/src/hooks/auto_capture 2.py +0 -208
- package/src/hooks/caffeinate-guard 2.sh +0 -8
- package/src/hooks/capture-session 2.sh +0 -21
- package/src/hooks/capture-tool-logs 2.sh +0 -127
- package/src/hooks/daily-briefing-check 2.sh +0 -33
- package/src/hooks/inbox-hook 2.sh +0 -76
- package/src/hooks/post-compact 2.sh +0 -148
- package/src/hooks/pre-compact 2.sh +0 -151
- package/src/hooks/session-start 2.sh +0 -268
- package/src/hooks/session-stop 2.sh +0 -140
- package/src/kg_populate 2.py +0 -290
- package/src/knowledge_graph 2.py +0 -257
- package/src/maintenance 2.py +0 -59
- package/src/migrate_embeddings 2.py +0 -122
- package/src/plugin_loader 2.py +0 -202
- package/src/plugins/__init__ 2.py +0 -0
- package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
- 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 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/adaptive_mode 2.py +0 -805
- package/src/plugins/agents 2.py +0 -52
- package/src/plugins/artifact_registry 2.py +0 -450
- package/src/plugins/backup 2.py +0 -104
- package/src/plugins/cognitive_memory 2.py +0 -564
- package/src/plugins/core_rules 2.py +0 -252
- package/src/plugins/cortex 2.py +0 -299
- package/src/plugins/entities 2.py +0 -67
- package/src/plugins/episodic_memory 2.py +0 -533
- package/src/plugins/evolution 2.py +0 -115
- package/src/plugins/guard 2.py +0 -746
- package/src/plugins/knowledge_graph_tools 2.py +0 -105
- package/src/plugins/preferences 2.py +0 -47
- package/src/plugins/update 2.py +0 -256
- package/src/requirements 2.txt +0 -12
- package/src/rules/__init__ 2.py +0 -0
- package/src/rules/core-rules 2.json +0 -331
- package/src/rules/migrate 2.py +0 -207
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/check-context 2.py +0 -264
- package/src/scripts/nexo-auto-update 2.py +0 -6
- package/src/scripts/nexo-backup 2.sh +0 -25
- package/src/scripts/nexo-brain-activation 2.sh +0 -140
- package/src/scripts/nexo-catchup 2.py +0 -242
- package/src/scripts/nexo-cognitive-decay 2.py +0 -182
- package/src/scripts/nexo-daily-self-audit 2.py +0 -552
- package/src/scripts/nexo-deep-sleep 2.sh +0 -97
- package/src/scripts/nexo-evolution-run 2.py +0 -597
- package/src/scripts/nexo-followup-hygiene 2.py +0 -112
- package/src/scripts/nexo-github-monitor 2.py +0 -256
- package/src/scripts/nexo-immune 2.py +0 -927
- package/src/scripts/nexo-inbox-hook 2.sh +0 -74
- package/src/scripts/nexo-install 2.py +0 -6
- package/src/scripts/nexo-learning-housekeep 2.py +0 -245
- package/src/scripts/nexo-learning-validator 2.py +0 -207
- package/src/scripts/nexo-migrate 2.py +0 -232
- package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
- package/src/scripts/nexo-pre-commit 2.py +0 -120
- package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
- package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
- package/src/scripts/nexo-reflection 2.py +0 -253
- package/src/scripts/nexo-runtime-preflight 2.py +0 -274
- package/src/scripts/nexo-send-email 2.py +0 -25
- package/src/scripts/nexo-send-email.py +0 -25
- package/src/scripts/nexo-send-reply 2.py +0 -178
- package/src/scripts/nexo-send-reply.py +0 -178
- package/src/scripts/nexo-sleep 2.py +0 -592
- package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
- package/src/scripts/nexo-synthesis 2.py +0 -253
- package/src/scripts/nexo-tcc-approve 2.sh +0 -79
- package/src/scripts/nexo-update 2.sh +0 -161
- package/src/scripts/nexo-watchdog 2.sh +0 -878
- package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
- package/src/server 2.py +0 -733
- package/src/storage_router 2.py +0 -32
- package/src/tools_coordination 2.py +0 -102
- package/src/tools_credentials 2.py +0 -68
- package/src/tools_learnings 2.py +0 -220
- package/src/tools_menu 2.py +0 -227
- package/src/tools_reminders 2.py +0 -86
- package/src/tools_reminders_crud 2.py +0 -159
- package/src/tools_sessions 2.py +0 -476
- package/src/tools_task_history 2.py +0 -57
- package/templates/CLAUDE.md 2.template +0 -63
- package/templates/openclaw 2.json +0 -13
- package/tests/__init__ 2.py +0 -0
- package/tests/__init__.py +0 -0
- package/tests/conftest 2.py +0 -71
- package/tests/conftest.py +0 -71
- package/tests/test_cognitive 2.py +0 -205
- package/tests/test_cognitive.py +0 -205
- package/tests/test_knowledge_graph 2.py +0 -140
- package/tests/test_knowledge_graph.py +0 -140
- package/tests/test_migrations 2.py +0 -137
- 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
|
-
|
|
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
|
-
|
|
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}|{
|
|
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
|
-
|
|
119
|
-
|
|
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
|
|
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
|
-
$
|
|
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
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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 "
|
|
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:
|
|
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
|
|
390
|
-
|
|
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:
|
|
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}
|
|
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"
|
|
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
|
-
|
|
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
|
|
894
|
+
# Only runs when NEXO_MAINTAINER=1 and NEXO_PUBLIC_REPO is configured
|
|
832
895
|
PROPAGATE_BLOCK=""
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
package/src/tools_sessions.py
CHANGED