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
package/src/auto_update 2.py
DELETED
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
"""NEXO Auto-Update — lightweight startup check for git updates and file-based migrations.
|
|
2
|
-
|
|
3
|
-
Called once per server startup. Respects a 1-hour cooldown to avoid redundant checks.
|
|
4
|
-
Never blocks startup for more than 5 seconds. Logs errors and continues on failure.
|
|
5
|
-
|
|
6
|
-
This is separate from plugins/update.py which handles MANUAL updates with rollback.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
import re
|
|
12
|
-
import subprocess
|
|
13
|
-
import sys
|
|
14
|
-
import time
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
18
|
-
DATA_DIR = NEXO_HOME / "data"
|
|
19
|
-
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
20
|
-
|
|
21
|
-
# Repo root: go up from src/
|
|
22
|
-
SRC_DIR = Path(__file__).resolve().parent
|
|
23
|
-
REPO_DIR = SRC_DIR.parent
|
|
24
|
-
|
|
25
|
-
LAST_CHECK_FILE = DATA_DIR / "auto_update_last_check.json"
|
|
26
|
-
MIGRATION_VERSION_FILE = DATA_DIR / "migration_version"
|
|
27
|
-
CLAUDE_MD_VERSION_FILE = DATA_DIR / "claude_md_version.txt"
|
|
28
|
-
MIGRATIONS_DIR = REPO_DIR / "migrations"
|
|
29
|
-
TEMPLATE_FILE = REPO_DIR / "templates" / "CLAUDE.md.template"
|
|
30
|
-
|
|
31
|
-
CHECK_COOLDOWN_SECONDS = 3600 # 1 hour
|
|
32
|
-
GIT_TIMEOUT_SECONDS = 4 # stay well under the 5s total budget
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _log(msg: str):
|
|
36
|
-
"""Log to stderr with prefix."""
|
|
37
|
-
print(f"[NEXO auto-update] {msg}", file=sys.stderr)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _read_last_check() -> dict:
|
|
41
|
-
"""Read last check state from disk."""
|
|
42
|
-
try:
|
|
43
|
-
if LAST_CHECK_FILE.exists():
|
|
44
|
-
return json.loads(LAST_CHECK_FILE.read_text())
|
|
45
|
-
except Exception:
|
|
46
|
-
pass
|
|
47
|
-
return {}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _write_last_check(data: dict):
|
|
51
|
-
"""Persist last check state."""
|
|
52
|
-
try:
|
|
53
|
-
LAST_CHECK_FILE.write_text(json.dumps(data))
|
|
54
|
-
except Exception as e:
|
|
55
|
-
_log(f"Failed to write last-check file: {e}")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _is_git_repo() -> bool:
|
|
59
|
-
"""Check if REPO_DIR is inside a git repository."""
|
|
60
|
-
try:
|
|
61
|
-
result = subprocess.run(
|
|
62
|
-
["git", "rev-parse", "--is-inside-work-tree"],
|
|
63
|
-
cwd=str(REPO_DIR),
|
|
64
|
-
capture_output=True,
|
|
65
|
-
text=True,
|
|
66
|
-
timeout=GIT_TIMEOUT_SECONDS,
|
|
67
|
-
)
|
|
68
|
-
return result.returncode == 0 and result.stdout.strip() == "true"
|
|
69
|
-
except Exception:
|
|
70
|
-
return False
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _git(*args) -> tuple[int, str, str]:
|
|
74
|
-
"""Run a git command in REPO_DIR. Returns (returncode, stdout, stderr)."""
|
|
75
|
-
result = subprocess.run(
|
|
76
|
-
["git"] + list(args),
|
|
77
|
-
cwd=str(REPO_DIR),
|
|
78
|
-
capture_output=True,
|
|
79
|
-
text=True,
|
|
80
|
-
timeout=GIT_TIMEOUT_SECONDS,
|
|
81
|
-
)
|
|
82
|
-
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _read_package_version() -> str:
|
|
86
|
-
"""Read version from package.json."""
|
|
87
|
-
try:
|
|
88
|
-
pkg = REPO_DIR / "package.json"
|
|
89
|
-
if pkg.exists():
|
|
90
|
-
return json.loads(pkg.read_text()).get("version", "unknown")
|
|
91
|
-
except Exception:
|
|
92
|
-
pass
|
|
93
|
-
return "unknown"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# ── Git-based auto-update ────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
def _check_git_updates() -> str | None:
|
|
99
|
-
"""Fetch remote, compare HEAD, pull if behind. Returns status message or None."""
|
|
100
|
-
# Fetch (allow it to fail silently on network issues)
|
|
101
|
-
rc, _, fetch_err = _git("fetch", "--quiet")
|
|
102
|
-
if rc != 0:
|
|
103
|
-
_log(f"git fetch failed (network?): {fetch_err}")
|
|
104
|
-
return None # Can't check, skip silently
|
|
105
|
-
|
|
106
|
-
# Compare local HEAD vs remote tracking branch
|
|
107
|
-
rc, local_head, _ = _git("rev-parse", "HEAD")
|
|
108
|
-
if rc != 0:
|
|
109
|
-
return None
|
|
110
|
-
rc, remote_head, _ = _git("rev-parse", "@{u}")
|
|
111
|
-
if rc != 0:
|
|
112
|
-
# No upstream configured — skip
|
|
113
|
-
return None
|
|
114
|
-
|
|
115
|
-
if local_head == remote_head:
|
|
116
|
-
return None # Already up to date
|
|
117
|
-
|
|
118
|
-
# Check if we're behind (remote has commits we don't)
|
|
119
|
-
rc, merge_base, _ = _git("merge-base", "HEAD", "@{u}")
|
|
120
|
-
if rc != 0:
|
|
121
|
-
return None
|
|
122
|
-
|
|
123
|
-
if merge_base == remote_head:
|
|
124
|
-
# Local is AHEAD — don't pull
|
|
125
|
-
return None
|
|
126
|
-
if merge_base != local_head and merge_base != remote_head:
|
|
127
|
-
# Diverged — don't auto-pull, too risky
|
|
128
|
-
_log("Local and remote have diverged. Skipping auto-pull.")
|
|
129
|
-
return "diverged"
|
|
130
|
-
|
|
131
|
-
# We're behind — safe to fast-forward pull
|
|
132
|
-
old_version = _read_package_version()
|
|
133
|
-
rc, pull_out, pull_err = _git("pull", "--ff-only")
|
|
134
|
-
if rc != 0:
|
|
135
|
-
_log(f"git pull --ff-only failed: {pull_err}")
|
|
136
|
-
return None # Don't break anything
|
|
137
|
-
|
|
138
|
-
new_version = _read_package_version()
|
|
139
|
-
|
|
140
|
-
# Run DB migrations after pull
|
|
141
|
-
_run_db_migrations()
|
|
142
|
-
|
|
143
|
-
msg = f"Auto-updated: {old_version} -> {new_version}" if old_version != new_version else f"Auto-updated (v{new_version}, new commits)"
|
|
144
|
-
_log(msg)
|
|
145
|
-
return msg
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def _run_db_migrations():
|
|
149
|
-
"""Run NEXO's DB schema migrations (from db._schema) after a pull."""
|
|
150
|
-
try:
|
|
151
|
-
from db._schema import run_migrations
|
|
152
|
-
from db._core import get_db
|
|
153
|
-
conn = get_db()
|
|
154
|
-
applied = run_migrations(conn)
|
|
155
|
-
if applied > 0:
|
|
156
|
-
_log(f"Applied {applied} DB migration(s)")
|
|
157
|
-
except Exception as e:
|
|
158
|
-
_log(f"DB migration error (continuing): {e}")
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# ── npm version check (notify only) ─────────────────────────────────
|
|
162
|
-
|
|
163
|
-
def _check_npm_version() -> str | None:
|
|
164
|
-
"""For non-git installs: check npm registry for a newer version. Returns notification or None."""
|
|
165
|
-
current = _read_package_version()
|
|
166
|
-
if current == "unknown":
|
|
167
|
-
return None
|
|
168
|
-
|
|
169
|
-
pkg_name = "nexo-brain"
|
|
170
|
-
try:
|
|
171
|
-
pkg = REPO_DIR / "package.json"
|
|
172
|
-
if pkg.exists():
|
|
173
|
-
data = json.loads(pkg.read_text())
|
|
174
|
-
pkg_name = data.get("name", pkg_name)
|
|
175
|
-
except Exception:
|
|
176
|
-
pass
|
|
177
|
-
|
|
178
|
-
try:
|
|
179
|
-
result = subprocess.run(
|
|
180
|
-
["npm", "view", pkg_name, "version"],
|
|
181
|
-
capture_output=True,
|
|
182
|
-
text=True,
|
|
183
|
-
timeout=GIT_TIMEOUT_SECONDS,
|
|
184
|
-
)
|
|
185
|
-
if result.returncode != 0:
|
|
186
|
-
return None
|
|
187
|
-
latest = result.stdout.strip()
|
|
188
|
-
if not latest:
|
|
189
|
-
return None
|
|
190
|
-
if latest != current and not current.endswith(latest):
|
|
191
|
-
return f"NEXO update available: {current} -> {latest}. Run: npm update -g {pkg_name}"
|
|
192
|
-
except Exception:
|
|
193
|
-
pass
|
|
194
|
-
return None
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# ── File-based migrations (migrations/ directory) ────────────────────
|
|
198
|
-
|
|
199
|
-
def _get_applied_migration_version() -> int:
|
|
200
|
-
"""Read the last applied file-migration version from disk."""
|
|
201
|
-
try:
|
|
202
|
-
if MIGRATION_VERSION_FILE.exists():
|
|
203
|
-
return int(MIGRATION_VERSION_FILE.read_text().strip())
|
|
204
|
-
except (ValueError, OSError):
|
|
205
|
-
pass
|
|
206
|
-
return 0
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def _set_migration_version(version: int):
|
|
210
|
-
"""Write the current file-migration version to disk."""
|
|
211
|
-
try:
|
|
212
|
-
MIGRATION_VERSION_FILE.write_text(str(version))
|
|
213
|
-
except Exception as e:
|
|
214
|
-
_log(f"Failed to write migration version: {e}")
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def _discover_migrations() -> list[tuple[int, Path]]:
|
|
218
|
-
"""Find numbered migration files in migrations/ directory.
|
|
219
|
-
|
|
220
|
-
Expected naming: NNN_description.ext where ext is .sql, .py, or .sh
|
|
221
|
-
Example: 001_add_index.sql, 002_backfill_data.py, 003_cleanup.sh
|
|
222
|
-
"""
|
|
223
|
-
if not MIGRATIONS_DIR.is_dir():
|
|
224
|
-
return []
|
|
225
|
-
|
|
226
|
-
migrations = []
|
|
227
|
-
for f in MIGRATIONS_DIR.iterdir():
|
|
228
|
-
if f.is_file() and f.suffix in (".sql", ".py", ".sh"):
|
|
229
|
-
# Extract leading number from filename
|
|
230
|
-
parts = f.stem.split("_", 1)
|
|
231
|
-
if parts and parts[0].isdigit():
|
|
232
|
-
migrations.append((int(parts[0]), f))
|
|
233
|
-
|
|
234
|
-
migrations.sort(key=lambda x: x[0])
|
|
235
|
-
return migrations
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def _run_file_migration(path: Path) -> tuple[bool, str]:
|
|
239
|
-
"""Execute a single migration file. Returns (success, message)."""
|
|
240
|
-
ext = path.suffix
|
|
241
|
-
|
|
242
|
-
try:
|
|
243
|
-
if ext == ".sql":
|
|
244
|
-
sql = path.read_text()
|
|
245
|
-
from db._core import get_db
|
|
246
|
-
conn = get_db()
|
|
247
|
-
conn.executescript(sql)
|
|
248
|
-
conn.commit()
|
|
249
|
-
return True, "OK"
|
|
250
|
-
|
|
251
|
-
elif ext == ".py":
|
|
252
|
-
result = subprocess.run(
|
|
253
|
-
[sys.executable, str(path)],
|
|
254
|
-
cwd=str(SRC_DIR),
|
|
255
|
-
capture_output=True,
|
|
256
|
-
text=True,
|
|
257
|
-
timeout=30,
|
|
258
|
-
env={**os.environ, "NEXO_HOME": str(NEXO_HOME)},
|
|
259
|
-
)
|
|
260
|
-
if result.returncode != 0:
|
|
261
|
-
return False, result.stderr or result.stdout or "non-zero exit"
|
|
262
|
-
return True, "OK"
|
|
263
|
-
|
|
264
|
-
elif ext == ".sh":
|
|
265
|
-
result = subprocess.run(
|
|
266
|
-
["bash", str(path)],
|
|
267
|
-
cwd=str(REPO_DIR),
|
|
268
|
-
capture_output=True,
|
|
269
|
-
text=True,
|
|
270
|
-
timeout=30,
|
|
271
|
-
env={**os.environ, "NEXO_HOME": str(NEXO_HOME)},
|
|
272
|
-
)
|
|
273
|
-
if result.returncode != 0:
|
|
274
|
-
return False, result.stderr or result.stdout or "non-zero exit"
|
|
275
|
-
return True, "OK"
|
|
276
|
-
|
|
277
|
-
else:
|
|
278
|
-
return False, f"unknown extension: {ext}"
|
|
279
|
-
|
|
280
|
-
except Exception as e:
|
|
281
|
-
return False, str(e)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def run_file_migrations() -> list[dict]:
|
|
285
|
-
"""Run any pending file-based migrations from the migrations/ directory.
|
|
286
|
-
|
|
287
|
-
Returns list of results: [{"version": N, "file": "...", "status": "ok"|"failed", "message": "..."}]
|
|
288
|
-
"""
|
|
289
|
-
current_version = _get_applied_migration_version()
|
|
290
|
-
migrations = _discover_migrations()
|
|
291
|
-
results = []
|
|
292
|
-
|
|
293
|
-
for version, path in migrations:
|
|
294
|
-
if version <= current_version:
|
|
295
|
-
continue
|
|
296
|
-
|
|
297
|
-
success, message = _run_file_migration(path)
|
|
298
|
-
|
|
299
|
-
if success:
|
|
300
|
-
_set_migration_version(version)
|
|
301
|
-
results.append({
|
|
302
|
-
"version": version,
|
|
303
|
-
"file": path.name,
|
|
304
|
-
"status": "ok",
|
|
305
|
-
"message": message,
|
|
306
|
-
})
|
|
307
|
-
_log(f"Migration {path.name}: OK")
|
|
308
|
-
else:
|
|
309
|
-
results.append({
|
|
310
|
-
"version": version,
|
|
311
|
-
"file": path.name,
|
|
312
|
-
"status": "failed",
|
|
313
|
-
"message": message,
|
|
314
|
-
})
|
|
315
|
-
_log(f"Migration {path.name}: FAILED — {message}")
|
|
316
|
-
# Don't advance version past a failure, but continue trying others
|
|
317
|
-
# so independent migrations still run. Version stays at last success.
|
|
318
|
-
|
|
319
|
-
return results
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
# ── CLAUDE.md version-tracked migration ─────────────────────────────
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def _read_template_version() -> str | None:
|
|
326
|
-
"""Extract version from the <!-- nexo-claude-md-version: X.Y.Z --> comment in the template."""
|
|
327
|
-
if not TEMPLATE_FILE.exists():
|
|
328
|
-
return None
|
|
329
|
-
first_line = TEMPLATE_FILE.read_text().split("\n", 1)[0]
|
|
330
|
-
m = re.search(r"nexo-claude-md-version:\s*([\d.]+)", first_line)
|
|
331
|
-
return m.group(1) if m else None
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def _read_installed_claude_md_version() -> str | None:
|
|
335
|
-
"""Read the CLAUDE.md version currently installed for this user."""
|
|
336
|
-
try:
|
|
337
|
-
if CLAUDE_MD_VERSION_FILE.exists():
|
|
338
|
-
return CLAUDE_MD_VERSION_FILE.read_text().strip()
|
|
339
|
-
except OSError:
|
|
340
|
-
pass
|
|
341
|
-
return None
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def _write_installed_claude_md_version(version: str):
|
|
345
|
-
"""Persist the installed CLAUDE.md version."""
|
|
346
|
-
try:
|
|
347
|
-
CLAUDE_MD_VERSION_FILE.write_text(version)
|
|
348
|
-
except Exception as e:
|
|
349
|
-
_log(f"Failed to write CLAUDE.md version file: {e}")
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def _find_user_claude_md() -> Path | None:
|
|
353
|
-
"""Locate the user's CLAUDE.md (typically ~/.claude/CLAUDE.md)."""
|
|
354
|
-
candidate = Path.home() / ".claude" / "CLAUDE.md"
|
|
355
|
-
if candidate.exists():
|
|
356
|
-
return candidate
|
|
357
|
-
# Fallback: check NEXO_HOME
|
|
358
|
-
candidate2 = NEXO_HOME / "CLAUDE.md"
|
|
359
|
-
if candidate2.exists():
|
|
360
|
-
return candidate2
|
|
361
|
-
return None
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def _resolve_placeholders(template_text: str) -> str:
|
|
365
|
-
"""Fill {{NAME}} and {{NEXO_HOME}} from the user's existing CLAUDE.md or config."""
|
|
366
|
-
# Try to read operator name from version.json
|
|
367
|
-
name = "NEXO"
|
|
368
|
-
try:
|
|
369
|
-
vf = NEXO_HOME / "version.json"
|
|
370
|
-
if vf.exists():
|
|
371
|
-
data = json.loads(vf.read_text())
|
|
372
|
-
name = data.get("operator_name", name)
|
|
373
|
-
except Exception:
|
|
374
|
-
pass
|
|
375
|
-
|
|
376
|
-
return (
|
|
377
|
-
template_text
|
|
378
|
-
.replace("{{NAME}}", name)
|
|
379
|
-
.replace("{{NEXO_HOME}}", str(NEXO_HOME))
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
def _extract_section(text: str, section_id: str) -> str | None:
|
|
384
|
-
"""Extract content between <!-- nexo:start:ID --> and <!-- nexo:end:ID --> markers (inclusive)."""
|
|
385
|
-
pattern = re.compile(
|
|
386
|
-
rf"(<!-- nexo:start:{re.escape(section_id)} -->.*?<!-- nexo:end:{re.escape(section_id)} -->)",
|
|
387
|
-
re.DOTALL,
|
|
388
|
-
)
|
|
389
|
-
m = pattern.search(text)
|
|
390
|
-
return m.group(1) if m else None
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
def _list_section_ids(text: str) -> list[str]:
|
|
394
|
-
"""Return all section IDs found in the text, in order."""
|
|
395
|
-
return re.findall(r"<!-- nexo:start:(\w+) -->", text)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
def _migrate_claude_md() -> str | None:
|
|
399
|
-
"""Compare template version vs installed version. If newer, update core sections in user's CLAUDE.md.
|
|
400
|
-
|
|
401
|
-
Returns a status message or None if no migration was needed.
|
|
402
|
-
"""
|
|
403
|
-
template_version = _read_template_version()
|
|
404
|
-
if not template_version:
|
|
405
|
-
return None
|
|
406
|
-
|
|
407
|
-
installed_version = _read_installed_claude_md_version()
|
|
408
|
-
if installed_version == template_version:
|
|
409
|
-
return None # Already up to date
|
|
410
|
-
|
|
411
|
-
user_md_path = _find_user_claude_md()
|
|
412
|
-
if not user_md_path:
|
|
413
|
-
_log("CLAUDE.md migration: no user CLAUDE.md found, skipping")
|
|
414
|
-
return None
|
|
415
|
-
|
|
416
|
-
# Read both files
|
|
417
|
-
user_md = user_md_path.read_text()
|
|
418
|
-
template_raw = TEMPLATE_FILE.read_text()
|
|
419
|
-
template_resolved = _resolve_placeholders(template_raw)
|
|
420
|
-
|
|
421
|
-
# Get all section IDs from the template
|
|
422
|
-
section_ids = _list_section_ids(template_resolved)
|
|
423
|
-
if not section_ids:
|
|
424
|
-
_log("CLAUDE.md migration: no section markers in template, skipping")
|
|
425
|
-
_write_installed_claude_md_version(template_version)
|
|
426
|
-
return None
|
|
427
|
-
|
|
428
|
-
updated = user_md
|
|
429
|
-
sections_replaced = 0
|
|
430
|
-
sections_added = 0
|
|
431
|
-
|
|
432
|
-
for sid in section_ids:
|
|
433
|
-
new_section = _extract_section(template_resolved, sid)
|
|
434
|
-
if not new_section:
|
|
435
|
-
continue
|
|
436
|
-
|
|
437
|
-
old_section = _extract_section(updated, sid)
|
|
438
|
-
if old_section:
|
|
439
|
-
if old_section != new_section:
|
|
440
|
-
updated = updated.replace(old_section, new_section)
|
|
441
|
-
sections_replaced += 1
|
|
442
|
-
else:
|
|
443
|
-
# Section doesn't exist in user's file — append before the end
|
|
444
|
-
# (new sections added by template updates)
|
|
445
|
-
updated = updated.rstrip() + "\n\n" + new_section + "\n"
|
|
446
|
-
sections_added += 1
|
|
447
|
-
|
|
448
|
-
# Update the version comment if present in user's file
|
|
449
|
-
updated = re.sub(
|
|
450
|
-
r"<!-- nexo-claude-md-version: [\d.]+ -->",
|
|
451
|
-
f"<!-- nexo-claude-md-version: {template_version} -->",
|
|
452
|
-
updated,
|
|
453
|
-
)
|
|
454
|
-
# If no version comment existed, add one at the top
|
|
455
|
-
if "nexo-claude-md-version:" not in updated:
|
|
456
|
-
updated = f"<!-- nexo-claude-md-version: {template_version} -->\n" + updated
|
|
457
|
-
|
|
458
|
-
if sections_replaced > 0 or sections_added > 0:
|
|
459
|
-
# Backup before writing
|
|
460
|
-
backup_path = user_md_path.with_suffix(".md.bak")
|
|
461
|
-
try:
|
|
462
|
-
backup_path.write_text(user_md)
|
|
463
|
-
except Exception:
|
|
464
|
-
pass # Non-critical
|
|
465
|
-
|
|
466
|
-
user_md_path.write_text(updated)
|
|
467
|
-
|
|
468
|
-
_write_installed_claude_md_version(template_version)
|
|
469
|
-
|
|
470
|
-
if sections_replaced == 0 and sections_added == 0:
|
|
471
|
-
return f"CLAUDE.md v{template_version}: already current (version file updated)"
|
|
472
|
-
|
|
473
|
-
msg = f"CLAUDE.md migrated to v{template_version}: {sections_replaced} section(s) updated, {sections_added} new section(s) added"
|
|
474
|
-
_log(msg)
|
|
475
|
-
return msg
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
# ── Main entry point ─────────────────────────────────────────────────
|
|
479
|
-
|
|
480
|
-
def auto_update_check() -> dict:
|
|
481
|
-
"""Run the full auto-update check at server startup.
|
|
482
|
-
|
|
483
|
-
NEVER raises an exception — always returns a dict.
|
|
484
|
-
|
|
485
|
-
Phase 1 (local, safe, no network):
|
|
486
|
-
- DB schema migrations
|
|
487
|
-
- File-based migrations
|
|
488
|
-
- CLAUDE.md version migration
|
|
489
|
-
|
|
490
|
-
Phase 2 (network, wrapped in try/except):
|
|
491
|
-
- git fetch/pull (if git repo)
|
|
492
|
-
- npm version check (if non-git install)
|
|
493
|
-
|
|
494
|
-
Returns a dict with:
|
|
495
|
-
- checked: bool — whether a network check was actually performed
|
|
496
|
-
- git_update: str|None — git update status message
|
|
497
|
-
- npm_notice: str|None — npm upgrade notice for non-git installs
|
|
498
|
-
- claude_md_update: str|None — CLAUDE.md migration status
|
|
499
|
-
- migrations: list — file-based migration results
|
|
500
|
-
- db_migrations: int — number of DB schema migrations applied
|
|
501
|
-
- skipped_reason: str|None — why the network check was skipped (cooldown, etc.)
|
|
502
|
-
- error: str|None — error message if something failed (informational only)
|
|
503
|
-
"""
|
|
504
|
-
result = {
|
|
505
|
-
"checked": False,
|
|
506
|
-
"git_update": None,
|
|
507
|
-
"npm_notice": None,
|
|
508
|
-
"claude_md_update": None,
|
|
509
|
-
"migrations": [],
|
|
510
|
-
"db_migrations": 0,
|
|
511
|
-
"skipped_reason": None,
|
|
512
|
-
"error": None,
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
# ── Read auto_update flag from schedule.json ────────────────────
|
|
516
|
-
auto_update_enabled = True
|
|
517
|
-
try:
|
|
518
|
-
schedule_file = NEXO_HOME / "config" / "schedule.json"
|
|
519
|
-
if schedule_file.exists():
|
|
520
|
-
schedule_data = json.loads(schedule_file.read_text())
|
|
521
|
-
auto_update_enabled = schedule_data.get("auto_update", True)
|
|
522
|
-
except Exception:
|
|
523
|
-
pass # Default to enabled on any read error
|
|
524
|
-
|
|
525
|
-
# ── Phase 1: Local migrations (safe, no network) ────────────────
|
|
526
|
-
# These ALWAYS run, regardless of cooldown, network state, or auto_update flag.
|
|
527
|
-
|
|
528
|
-
# DB schema migrations
|
|
529
|
-
try:
|
|
530
|
-
_run_db_migrations()
|
|
531
|
-
except Exception as e:
|
|
532
|
-
_log(f"DB migration error (continuing): {e}")
|
|
533
|
-
|
|
534
|
-
# File-based migrations
|
|
535
|
-
try:
|
|
536
|
-
result["migrations"] = run_file_migrations()
|
|
537
|
-
except Exception as e:
|
|
538
|
-
_log(f"File migration runner error: {e}")
|
|
539
|
-
|
|
540
|
-
# Backfill evolution-objective.json for existing installs
|
|
541
|
-
try:
|
|
542
|
-
evo_obj_path = NEXO_HOME / "brain" / "evolution-objective.json"
|
|
543
|
-
if not evo_obj_path.exists():
|
|
544
|
-
(NEXO_HOME / "brain").mkdir(parents=True, exist_ok=True)
|
|
545
|
-
default_objective = {
|
|
546
|
-
"objective": "Improve operational excellence and reduce repeated errors",
|
|
547
|
-
"focus_areas": ["error_prevention", "proactivity", "memory_quality"],
|
|
548
|
-
"evolution_enabled": True,
|
|
549
|
-
"evolution_mode": "review",
|
|
550
|
-
"dimensions": {
|
|
551
|
-
"episodic_memory": {"current": 0, "target": 90},
|
|
552
|
-
"autonomy": {"current": 0, "target": 80},
|
|
553
|
-
"proactivity": {"current": 0, "target": 70},
|
|
554
|
-
"self_improvement": {"current": 0, "target": 60},
|
|
555
|
-
"agi": {"current": 0, "target": 20},
|
|
556
|
-
},
|
|
557
|
-
"total_evolutions": 0,
|
|
558
|
-
"consecutive_failures": 0,
|
|
559
|
-
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
|
560
|
-
}
|
|
561
|
-
evo_obj_path.write_text(json.dumps(default_objective, indent=2))
|
|
562
|
-
_log("Backfilled evolution-objective.json for existing install")
|
|
563
|
-
except Exception as e:
|
|
564
|
-
_log(f"evolution-objective.json backfill error: {e}")
|
|
565
|
-
|
|
566
|
-
# Backfill NEXO_HOME/scripts/ for existing installs
|
|
567
|
-
try:
|
|
568
|
-
scripts_dest = NEXO_HOME / "scripts"
|
|
569
|
-
# Deduce NEXO_CODE: env var first, then from __file__ (auto_update.py is in src/)
|
|
570
|
-
nexo_code = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parent)))
|
|
571
|
-
scripts_src = nexo_code / "scripts" if (nexo_code / "scripts").is_dir() else None
|
|
572
|
-
if scripts_src and not scripts_dest.is_dir():
|
|
573
|
-
import shutil
|
|
574
|
-
scripts_dest.mkdir(parents=True, exist_ok=True)
|
|
575
|
-
for f in scripts_src.iterdir():
|
|
576
|
-
if f.name.startswith('.') or f.name == '__pycache__':
|
|
577
|
-
continue
|
|
578
|
-
dest = scripts_dest / f.name
|
|
579
|
-
if f.is_file() and not dest.exists():
|
|
580
|
-
shutil.copy2(str(f), str(dest))
|
|
581
|
-
_log("Backfilled NEXO_HOME/scripts/ from NEXO_CODE for existing install")
|
|
582
|
-
except Exception as e:
|
|
583
|
-
_log(f"scripts backfill error: {e}")
|
|
584
|
-
|
|
585
|
-
# CLAUDE.md version migration
|
|
586
|
-
try:
|
|
587
|
-
result["claude_md_update"] = _migrate_claude_md()
|
|
588
|
-
except Exception as e:
|
|
589
|
-
_log(f"CLAUDE.md migration error: {e}")
|
|
590
|
-
|
|
591
|
-
# ── Phase 2: Network operations (wrapped, never fatal) ──────────
|
|
592
|
-
# Skip entirely if auto_update is disabled in schedule.json
|
|
593
|
-
if not auto_update_enabled:
|
|
594
|
-
result["skipped_reason"] = "auto_update disabled in schedule.json"
|
|
595
|
-
_log("Network updates disabled (auto_update: false in schedule.json)")
|
|
596
|
-
return result
|
|
597
|
-
|
|
598
|
-
# Check cooldown for git/npm checks
|
|
599
|
-
try:
|
|
600
|
-
last_check = _read_last_check()
|
|
601
|
-
last_ts = last_check.get("timestamp", 0)
|
|
602
|
-
now = time.time()
|
|
603
|
-
|
|
604
|
-
if now - last_ts < CHECK_COOLDOWN_SECONDS:
|
|
605
|
-
result["skipped_reason"] = "cooldown"
|
|
606
|
-
return result
|
|
607
|
-
|
|
608
|
-
result["checked"] = True
|
|
609
|
-
|
|
610
|
-
is_git = _is_git_repo()
|
|
611
|
-
|
|
612
|
-
if is_git:
|
|
613
|
-
result["git_update"] = _check_git_updates()
|
|
614
|
-
else:
|
|
615
|
-
# Non-git install — check npm for newer version
|
|
616
|
-
version_json = REPO_DIR / "version.json"
|
|
617
|
-
pkg_json = REPO_DIR / "package.json"
|
|
618
|
-
if version_json.exists() or pkg_json.exists():
|
|
619
|
-
result["npm_notice"] = _check_npm_version()
|
|
620
|
-
|
|
621
|
-
# Save timestamp
|
|
622
|
-
_write_last_check({
|
|
623
|
-
"timestamp": now,
|
|
624
|
-
"is_git": is_git,
|
|
625
|
-
"git_update": result["git_update"],
|
|
626
|
-
"npm_notice": result["npm_notice"],
|
|
627
|
-
})
|
|
628
|
-
|
|
629
|
-
except Exception as e:
|
|
630
|
-
error_msg = f"Update check failed: {e}. Running current version."
|
|
631
|
-
_log(error_msg)
|
|
632
|
-
result["error"] = error_msg
|
|
633
|
-
|
|
634
|
-
return result
|