nexo-brain 1.7.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -60
- package/bin/nexo-brain.js +680 -381
- package/package.json +18 -3
- 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_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__/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.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-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.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/crons/manifest.json +106 -0
- package/src/crons/sync.py +217 -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.py +24 -4
- package/src/dashboard/templates/dashboard.html +3 -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 +2 -4
- package/src/db/_reminders.py +45 -6
- package/src/db/_schema.py +31 -0
- package/src/evolution_cycle.py +33 -11
- 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 +4 -2
- 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__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.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__/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__/evolution.cpython-310.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 +37 -16
- 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 +256 -0
- package/src/requirements.txt +12 -0
- package/src/scripts/check-context.py +8 -3
- package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/apply_findings.py +514 -169
- package/src/scripts/deep-sleep/collect.py +480 -0
- package/src/scripts/deep-sleep/extract-prompt.md +233 -0
- package/src/scripts/deep-sleep/extract.py +249 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +168 -0
- package/src/scripts/deep-sleep/synthesize.py +191 -0
- 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 -25
- package/src/scripts/nexo-cognitive-decay.py +7 -3
- package/src/scripts/nexo-daily-self-audit.py +44 -15
- package/src/scripts/nexo-deep-sleep.sh +31 -16
- package/src/scripts/nexo-evolution-run.py +21 -11
- package/src/scripts/nexo-followup-hygiene.py +6 -4
- package/src/scripts/nexo-github-monitor.py +12 -8
- package/src/scripts/nexo-immune.py +6 -4
- 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 +1 -22
- package/src/scripts/nexo-migrate.py +9 -3
- package/src/scripts/nexo-postmortem-consolidator.py +17 -10
- 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 +5 -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 +13 -9
- package/src/scripts/nexo-snapshot-restore.sh +2 -1
- package/src/scripts/nexo-synthesis.py +11 -7
- 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_menu.py +1 -1
- package/src/tools_reminders_crud.py +10 -8
- package/src/tools_sessions.py +76 -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/conftest.py +2 -2
- package/tests/test_cognitive.py +7 -6
- package/tests/test_migrations.py +26 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
- package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- 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-github-monitor.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/deep-sleep/__pycache__/analyze_session.cpython-314.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-314.pyc +0 -0
- package/src/scripts/deep-sleep/analyze_session.py +0 -217
- package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
- package/src/scripts/deep-sleep/prompt.md +0 -109
- package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -27,6 +27,7 @@ Options:
|
|
|
27
27
|
import argparse
|
|
28
28
|
import imaplib
|
|
29
29
|
import json
|
|
30
|
+
import os
|
|
30
31
|
import smtplib
|
|
31
32
|
import sys
|
|
32
33
|
import time
|
|
@@ -35,7 +36,8 @@ from email.utils import make_msgid, formatdate
|
|
|
35
36
|
from pathlib import Path
|
|
36
37
|
import mimetypes
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
40
|
+
CONFIG_PATH = NEXO_HOME / "nexo-email" / "config.json"
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def load_config():
|
|
@@ -32,7 +32,7 @@ from pathlib import Path
|
|
|
32
32
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
33
33
|
|
|
34
34
|
# ─── Paths ────────────────────────────────────────────────────────────────────
|
|
35
|
-
CLAUDE_DIR =
|
|
35
|
+
CLAUDE_DIR = NEXO_HOME
|
|
36
36
|
BRAIN_DIR = CLAUDE_DIR / "brain"
|
|
37
37
|
COORD_DIR = CLAUDE_DIR / "coordination"
|
|
38
38
|
MEMORY_DIR = CLAUDE_DIR / "memory"
|
|
@@ -46,8 +46,8 @@ HEARTBEAT_LOG = COORD_DIR / "heartbeat-log.json"
|
|
|
46
46
|
REFLECTION_LOG = COORD_DIR / "reflection-log.json"
|
|
47
47
|
SLEEP_LOG = COORD_DIR / "sleep-log.json"
|
|
48
48
|
|
|
49
|
-
MEMORY_MD =
|
|
50
|
-
NEXO_DB =
|
|
49
|
+
MEMORY_MD = NEXO_HOME / "memory" / "MEMORY.md"
|
|
50
|
+
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
51
51
|
CLAUDE_MEM_DB = Path.home() / ".claude-mem" / "claude-mem.db"
|
|
52
52
|
CLAUDE_CLI = Path.home() / ".local" / "bin" / "claude"
|
|
53
53
|
|
|
@@ -252,7 +252,7 @@ def stage_a_cleanup() -> dict:
|
|
|
252
252
|
|
|
253
253
|
# A8: Delete cortex/logs/*.log >7 days, truncate launchd >5MB
|
|
254
254
|
cutoff_7 = TODAY - timedelta(days=7)
|
|
255
|
-
cortex_logs =
|
|
255
|
+
cortex_logs = NEXO_HOME / "cortex" / "logs"
|
|
256
256
|
if cortex_logs.exists():
|
|
257
257
|
for f in cortex_logs.glob("*.log"):
|
|
258
258
|
if f.name.startswith("launchd-"):
|
|
@@ -383,9 +383,12 @@ The wrapper will handle the actual DB cleanup safely.""")
|
|
|
383
383
|
|
|
384
384
|
tasks_str = "\n\n".join(tasks)
|
|
385
385
|
|
|
386
|
-
prompt = f"""
|
|
386
|
+
prompt = f"""FIRST: Call nexo_startup(task='deep-sleep nightly maintenance') to register this session.
|
|
387
|
+
|
|
388
|
+
You are NEXO Sleep — the nightly brain maintenance process.
|
|
387
389
|
Like a human brain during sleep: consolidate important memories, discard noise,
|
|
388
390
|
detect conflicts, prepare state for tomorrow.
|
|
391
|
+
Use nexo_learning_add, nexo_followup_create, nexo_session_diary_write and other MCP tools directly.
|
|
389
392
|
|
|
390
393
|
BRAIN STATE:
|
|
391
394
|
- {len(state['learnings'])} active learnings
|
|
@@ -407,16 +410,17 @@ Write a summary to {COORD_DIR}/sleep-report.md when done.
|
|
|
407
410
|
Execute without asking."""
|
|
408
411
|
|
|
409
412
|
log("Stage B: Invoking Claude CLI (opus) — dreaming...")
|
|
410
|
-
|
|
411
413
|
env = os.environ.copy()
|
|
414
|
+
env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
|
|
412
415
|
env.pop("CLAUDECODE", None)
|
|
413
416
|
env.pop("CLAUDE_CODE", None)
|
|
414
417
|
|
|
415
418
|
try:
|
|
416
419
|
result = subprocess.run(
|
|
417
420
|
[str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
|
|
418
|
-
"--
|
|
419
|
-
|
|
421
|
+
"--output-format", "text",
|
|
422
|
+
"--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
|
|
423
|
+
capture_output=True, text=True, timeout=21600, env=env
|
|
420
424
|
)
|
|
421
425
|
|
|
422
426
|
if result.returncode != 0:
|
|
@@ -568,7 +572,7 @@ def main():
|
|
|
568
572
|
|
|
569
573
|
# Register for catch-up
|
|
570
574
|
try:
|
|
571
|
-
state_file =
|
|
575
|
+
state_file = NEXO_HOME / "operations" / ".catchup-state.json"
|
|
572
576
|
st = json.loads(state_file.read_text()) if state_file.exists() else {}
|
|
573
577
|
st["sleep"] = datetime.now().isoformat()
|
|
574
578
|
state_file.write_text(json.dumps(st, indent=2))
|
|
@@ -5,7 +5,8 @@ set -euo pipefail
|
|
|
5
5
|
|
|
6
6
|
SNAP_DIR="${1:?Usage: nexo-snapshot-restore.sh <snapshot-dir>}"
|
|
7
7
|
MANIFEST="$SNAP_DIR/manifest.json"
|
|
8
|
-
|
|
8
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
9
|
+
RESTORE_LOG="$NEXO_HOME/logs/snapshot-restores.log"
|
|
9
10
|
|
|
10
11
|
if [ ! -f "$MANIFEST" ]; then
|
|
11
12
|
echo "ERROR: No manifest.json in $SNAP_DIR" >&2
|
|
@@ -19,9 +19,10 @@ from datetime import datetime, date, timedelta
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
21
|
HOME = Path.home()
|
|
22
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
22
23
|
CLAUDE_DIR = NEXO_HOME
|
|
23
24
|
COORD_DIR = CLAUDE_DIR / "coordination"
|
|
24
|
-
NEXO_DB = NEXO_HOME / "
|
|
25
|
+
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
25
26
|
OUTPUT_FILE = COORD_DIR / "daily-synthesis.md"
|
|
26
27
|
LAST_RUN_FILE = COORD_DIR / "synthesis-last-run"
|
|
27
28
|
LOCK_FILE = COORD_DIR / "synthesis.lock"
|
|
@@ -142,9 +143,11 @@ def synthesize(data: dict) -> bool:
|
|
|
142
143
|
if len(data_json) > 15000:
|
|
143
144
|
data_json = data_json[:15000] + "\n... (truncated)"
|
|
144
145
|
|
|
145
|
-
prompt = f"""
|
|
146
|
+
prompt = f"""FIRST: Call nexo_startup(task='daily synthesis') to register this session.
|
|
147
|
+
|
|
148
|
+
You are NEXO's synthesis engine. Write the daily intelligence brief for tomorrow's
|
|
146
149
|
startup. This file is read by NEXO at the beginning of each session to understand
|
|
147
|
-
what happened today and what to focus on tomorrow.
|
|
150
|
+
what happened today and what to focus on tomorrow. Use nexo_learning_add and nexo_followup_create if you discover actionable items.
|
|
148
151
|
|
|
149
152
|
TODAY'S RAW DATA:
|
|
150
153
|
{data_json}
|
|
@@ -181,17 +184,18 @@ not what merely happened. If a section has nothing, write "Nothing notable."
|
|
|
181
184
|
|
|
182
185
|
Execute without asking."""
|
|
183
186
|
|
|
184
|
-
log("Invoking Claude CLI (
|
|
185
|
-
|
|
187
|
+
log("Invoking Claude CLI (opus) for synthesis...")
|
|
186
188
|
env = os.environ.copy()
|
|
189
|
+
env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
|
|
187
190
|
env.pop("CLAUDECODE", None)
|
|
188
191
|
env.pop("CLAUDE_CODE", None)
|
|
189
192
|
|
|
190
193
|
try:
|
|
191
194
|
result = subprocess.run(
|
|
192
195
|
[str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
|
|
193
|
-
"--
|
|
194
|
-
|
|
196
|
+
"--output-format", "text",
|
|
197
|
+
"--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
|
|
198
|
+
capture_output=True, text=True, timeout=21600, env=env
|
|
195
199
|
)
|
|
196
200
|
|
|
197
201
|
if result.returncode != 0:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# NEXO TCC Auto-Approve — grants macOS permissions to new Claude Code versions.
|
|
3
|
+
#
|
|
4
|
+
# macOS only. On Linux this is a no-op (Linux doesn't have TCC).
|
|
5
|
+
# Runs at load to approve any new Claude versions that appeared.
|
|
6
|
+
#
|
|
7
|
+
# What it does:
|
|
8
|
+
# 1. Scans ~/.local/share/claude/versions/ for Claude binaries
|
|
9
|
+
# 2. For each new version, grants TCC access to Documents, Desktop, Downloads, etc.
|
|
10
|
+
# 3. Also approves the Python binary used by NEXO's venv
|
|
11
|
+
# 4. Tracks which versions have been approved to avoid re-processing
|
|
12
|
+
#
|
|
13
|
+
# Why: Claude Code updates frequently. Each new binary needs macOS permission
|
|
14
|
+
# grants or the user gets popup dialogs interrupting their work.
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# Linux: nothing to do
|
|
19
|
+
if [ "$(uname -s)" != "Darwin" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
24
|
+
TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
|
|
25
|
+
VERSIONS_DIR="$HOME/.local/share/claude/versions"
|
|
26
|
+
MARKER_DIR="$NEXO_HOME/data/.tcc-approved"
|
|
27
|
+
LOG="$NEXO_HOME/logs/tcc-auto-approve.log"
|
|
28
|
+
|
|
29
|
+
mkdir -p "$MARKER_DIR" "$(dirname "$LOG")"
|
|
30
|
+
|
|
31
|
+
# TCC services Claude Code needs
|
|
32
|
+
SERVICES=(
|
|
33
|
+
kTCCServiceSystemPolicyDocumentsFolder
|
|
34
|
+
kTCCServiceSystemPolicyDesktopFolder
|
|
35
|
+
kTCCServiceSystemPolicyDownloadsFolder
|
|
36
|
+
kTCCServiceMediaLibrary
|
|
37
|
+
kTCCServiceSystemPolicyNetworkVolumes
|
|
38
|
+
kTCCServiceSystemPolicyAppData
|
|
39
|
+
kTCCServiceFileProviderDomain
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Approve Claude versions
|
|
43
|
+
if [ -d "$VERSIONS_DIR" ]; then
|
|
44
|
+
for bin_path in "$VERSIONS_DIR"/*; do
|
|
45
|
+
[ ! -e "$bin_path" ] && continue
|
|
46
|
+
version=$(basename "$bin_path")
|
|
47
|
+
marker="$MARKER_DIR/$version"
|
|
48
|
+
|
|
49
|
+
# Skip if already approved
|
|
50
|
+
[ -f "$marker" ] && continue
|
|
51
|
+
|
|
52
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') Approving Claude $version" >> "$LOG"
|
|
53
|
+
|
|
54
|
+
for svc in "${SERVICES[@]}"; do
|
|
55
|
+
sqlite3 "$TCC_DB" "
|
|
56
|
+
INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version)
|
|
57
|
+
VALUES ('$svc', '$bin_path', 1, 2, 4, 1);
|
|
58
|
+
" 2>/dev/null
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
touch "$marker"
|
|
62
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') Done: Claude $version — ${#SERVICES[@]} services approved" >> "$LOG"
|
|
63
|
+
done
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Also approve Python from NEXO's venv (if it exists)
|
|
67
|
+
NEXO_CODE="${NEXO_CODE:-}"
|
|
68
|
+
if [ -n "$NEXO_CODE" ]; then
|
|
69
|
+
PYTHON_BIN="$(dirname "$NEXO_CODE")/.venv/bin/python"
|
|
70
|
+
if [ -e "$PYTHON_BIN" ]; then
|
|
71
|
+
PYTHON_REAL=$(readlink -f "$PYTHON_BIN" 2>/dev/null || echo "$PYTHON_BIN")
|
|
72
|
+
for svc in "${SERVICES[@]}"; do
|
|
73
|
+
sqlite3 "$TCC_DB" "
|
|
74
|
+
INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version)
|
|
75
|
+
VALUES ('$svc', '$PYTHON_REAL', 1, 2, 4, 1);
|
|
76
|
+
" 2>/dev/null
|
|
77
|
+
done
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# nexo-update.sh — Standalone NEXO update script
|
|
3
|
+
# Same logic as the MCP tool but usable when the server itself needs updating.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# nexo-update.sh # pull from origin main
|
|
7
|
+
# nexo-update.sh origin beta # pull from origin beta
|
|
8
|
+
# NEXO_HOME=/path nexo-update.sh # custom NEXO_HOME
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
# --- Configuration ---
|
|
13
|
+
REMOTE="${1:-origin}"
|
|
14
|
+
BRANCH="${2:-main}"
|
|
15
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
16
|
+
|
|
17
|
+
# Determine repo directory: script is at src/scripts/, repo root is ../../
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
20
|
+
SRC_DIR="$REPO_DIR/src"
|
|
21
|
+
PACKAGE_JSON="$REPO_DIR/package.json"
|
|
22
|
+
|
|
23
|
+
# --- Helpers ---
|
|
24
|
+
RED='\033[0;31m'
|
|
25
|
+
GREEN='\033[0;32m'
|
|
26
|
+
YELLOW='\033[1;33m'
|
|
27
|
+
NC='\033[0m'
|
|
28
|
+
|
|
29
|
+
log() { echo -e "${GREEN}[nexo-update]${NC} $*"; }
|
|
30
|
+
warn() { echo -e "${YELLOW}[nexo-update]${NC} $*"; }
|
|
31
|
+
err() { echo -e "${RED}[nexo-update]${NC} $*" >&2; }
|
|
32
|
+
|
|
33
|
+
read_version() {
|
|
34
|
+
python3 -c "import json; print(json.load(open('$PACKAGE_JSON')).get('version','unknown'))" 2>/dev/null || echo "unknown"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# --- Step 1: Check for uncommitted changes in src/ ---
|
|
38
|
+
log "Checking for uncommitted changes in src/..."
|
|
39
|
+
cd "$REPO_DIR"
|
|
40
|
+
|
|
41
|
+
if [ -n "$(git status --porcelain -- src/ 2>/dev/null)" ]; then
|
|
42
|
+
err "ABORTED: Uncommitted changes in src/"
|
|
43
|
+
git status --short -- src/
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
log "Working tree clean."
|
|
47
|
+
|
|
48
|
+
# Record current state
|
|
49
|
+
OLD_VERSION="$(read_version)"
|
|
50
|
+
OLD_COMMIT="$(git rev-parse HEAD)"
|
|
51
|
+
log "Current: v${OLD_VERSION} (${OLD_COMMIT:0:8})"
|
|
52
|
+
|
|
53
|
+
# --- Step 2: Backup databases ---
|
|
54
|
+
TIMESTAMP="$(date +%Y-%m-%d-%H%M)"
|
|
55
|
+
BACKUP_DIR="$NEXO_HOME/backups/pre-update-$TIMESTAMP"
|
|
56
|
+
|
|
57
|
+
backup_dbs() {
|
|
58
|
+
local found=0
|
|
59
|
+
# Check data/, NEXO_HOME root, and src/ for .db files
|
|
60
|
+
for dir in "$NEXO_HOME/data" "$NEXO_HOME" "$SRC_DIR"; do
|
|
61
|
+
if [ -d "$dir" ]; then
|
|
62
|
+
for db in "$dir"/*.db; do
|
|
63
|
+
[ -f "$db" ] || continue
|
|
64
|
+
found=1
|
|
65
|
+
mkdir -p "$BACKUP_DIR"
|
|
66
|
+
cp "$db" "$BACKUP_DIR/$(basename "$db")"
|
|
67
|
+
log " Backed up: $(basename "$db")"
|
|
68
|
+
done
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
if [ "$found" -eq 0 ]; then
|
|
72
|
+
log " No databases found to backup."
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log "Backing up databases..."
|
|
77
|
+
backup_dbs
|
|
78
|
+
|
|
79
|
+
# --- Step 3: git pull ---
|
|
80
|
+
log "Pulling from ${REMOTE}/${BRANCH}..."
|
|
81
|
+
PULL_OUTPUT="$(git pull "$REMOTE" "$BRANCH" 2>&1)" || {
|
|
82
|
+
err "git pull failed:"
|
|
83
|
+
err "$PULL_OUTPUT"
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
log "$PULL_OUTPUT"
|
|
87
|
+
|
|
88
|
+
if echo "$PULL_OUTPUT" | grep -q "Already up to date"; then
|
|
89
|
+
log "Already up to date (v${OLD_VERSION}). Done."
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# --- Step 4: Check version ---
|
|
94
|
+
NEW_VERSION="$(read_version)"
|
|
95
|
+
log "New version: v${NEW_VERSION}"
|
|
96
|
+
|
|
97
|
+
# --- Step 5: Run migrations if version changed ---
|
|
98
|
+
if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
|
|
99
|
+
log "Version changed: ${OLD_VERSION} -> ${NEW_VERSION}"
|
|
100
|
+
log "Running migrations..."
|
|
101
|
+
if ! (cd "$SRC_DIR" && python3 -c "import db; db.init_db()" 2>&1); then
|
|
102
|
+
err "Migration failed! Rolling back..."
|
|
103
|
+
git reset --hard "$OLD_COMMIT"
|
|
104
|
+
# Restore DB backups
|
|
105
|
+
if [ -d "$BACKUP_DIR" ]; then
|
|
106
|
+
for db in "$BACKUP_DIR"/*.db; do
|
|
107
|
+
[ -f "$db" ] || continue
|
|
108
|
+
BASENAME="$(basename "$db")"
|
|
109
|
+
for candidate in "$NEXO_HOME/data/$BASENAME" "$NEXO_HOME/$BASENAME" "$SRC_DIR/$BASENAME"; do
|
|
110
|
+
if [ -f "$candidate" ]; then
|
|
111
|
+
cp "$db" "$candidate"
|
|
112
|
+
warn " Restored: $BASENAME"
|
|
113
|
+
break
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
done
|
|
117
|
+
fi
|
|
118
|
+
err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
log "Migrations applied."
|
|
122
|
+
else
|
|
123
|
+
log "Version unchanged (${OLD_VERSION}), skipping migrations."
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# --- Step 6: Verify import ---
|
|
127
|
+
log "Verifying server.py import..."
|
|
128
|
+
if ! (cd "$SRC_DIR" && python3 -c "import server" 2>&1); then
|
|
129
|
+
err "Import verification failed! Rolling back..."
|
|
130
|
+
git reset --hard "$OLD_COMMIT"
|
|
131
|
+
if [ -d "$BACKUP_DIR" ]; then
|
|
132
|
+
for db in "$BACKUP_DIR"/*.db; do
|
|
133
|
+
[ -f "$db" ] || continue
|
|
134
|
+
BASENAME="$(basename "$db")"
|
|
135
|
+
for candidate in "$NEXO_HOME/data/$BASENAME" "$NEXO_HOME/$BASENAME" "$SRC_DIR/$BASENAME"; do
|
|
136
|
+
if [ -f "$candidate" ]; then
|
|
137
|
+
cp "$db" "$candidate"
|
|
138
|
+
warn " Restored: $BASENAME"
|
|
139
|
+
break
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
done
|
|
143
|
+
fi
|
|
144
|
+
err "Rolled back to ${OLD_COMMIT:0:8}. Databases restored."
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# --- Done ---
|
|
149
|
+
echo ""
|
|
150
|
+
log "========================================="
|
|
151
|
+
log " UPDATE SUCCESSFUL"
|
|
152
|
+
if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
|
|
153
|
+
log " Version: ${OLD_VERSION} -> ${NEW_VERSION}"
|
|
154
|
+
else
|
|
155
|
+
log " Version: ${OLD_VERSION} (unchanged)"
|
|
156
|
+
fi
|
|
157
|
+
log " Branch: ${REMOTE}/${BRANCH}"
|
|
158
|
+
log " Backup: ${BACKUP_DIR}"
|
|
159
|
+
log "========================================="
|
|
160
|
+
echo ""
|
|
161
|
+
warn "MCP server restart needed to load new code."
|
|
@@ -10,18 +10,19 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import hashlib
|
|
12
12
|
import json
|
|
13
|
+
import os
|
|
13
14
|
import sqlite3
|
|
14
15
|
import subprocess
|
|
15
16
|
from datetime import datetime
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
|
|
18
19
|
HOME = Path.home()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
LOG_DIR =
|
|
20
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(HOME / ".nexo")))
|
|
21
|
+
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(NEXO_HOME)))
|
|
22
|
+
BRAIN_DIR = NEXO_HOME / "brain"
|
|
23
|
+
LOG_DIR = NEXO_HOME / "logs"
|
|
23
24
|
SUMMARY_FILE = LOG_DIR / "watchdog-smoke-summary.json"
|
|
24
|
-
HASH_REGISTRY =
|
|
25
|
+
HASH_REGISTRY = NEXO_HOME / "scripts" / ".watchdog-hashes"
|
|
25
26
|
RESTORE_LOG = LOG_DIR / "snapshot-restores.log"
|
|
26
27
|
|
|
27
28
|
|
|
@@ -41,7 +42,7 @@ def main() -> int:
|
|
|
41
42
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
42
43
|
findings = []
|
|
43
44
|
|
|
44
|
-
db_path =
|
|
45
|
+
db_path = NEXO_HOME / "data" / "nexo.db"
|
|
45
46
|
integrity = "missing"
|
|
46
47
|
if db_path.exists():
|
|
47
48
|
try:
|
|
@@ -53,15 +54,16 @@ def main() -> int:
|
|
|
53
54
|
if integrity != "ok":
|
|
54
55
|
findings.append({"severity": "ERROR", "area": "sqlite", "msg": f"integrity={integrity}"})
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
# Check if the NEXO MCP server process is alive (replaces legacy cortex process check)
|
|
58
|
+
nexo_server_running = subprocess.run(
|
|
59
|
+
["pgrep", "-f", "nexo-brain"],
|
|
58
60
|
capture_output=True,
|
|
59
61
|
text=True,
|
|
60
62
|
).returncode == 0
|
|
61
|
-
if not
|
|
62
|
-
findings.append({"severity": "
|
|
63
|
+
if not nexo_server_running:
|
|
64
|
+
findings.append({"severity": "INFO", "area": "server", "msg": "nexo-brain not running (normal if no active session)"})
|
|
63
65
|
|
|
64
|
-
backups = sorted((
|
|
66
|
+
backups = sorted((NEXO_HOME / "backups").glob("nexo-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
65
67
|
if backups:
|
|
66
68
|
age_seconds = int(datetime.now().timestamp() - backups[0].stat().st_mtime)
|
|
67
69
|
if age_seconds > 7200:
|
|
@@ -84,7 +86,10 @@ def main() -> int:
|
|
|
84
86
|
elif restore_count > 0:
|
|
85
87
|
findings.append({"severity": "INFO", "area": "restore_activity", "msg": f"{restore_count} restores this hour"})
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
# Check brain/ (canonical) first, fall back to cortex/ (legacy)
|
|
90
|
+
objective = BRAIN_DIR / "evolution-objective.json"
|
|
91
|
+
if not objective.exists():
|
|
92
|
+
objective = NEXO_HOME / "cortex" / "evolution-objective.json"
|
|
88
93
|
evolution_enabled = None
|
|
89
94
|
if objective.exists():
|
|
90
95
|
obj = json.loads(objective.read_text())
|
|
@@ -100,7 +105,7 @@ def main() -> int:
|
|
|
100
105
|
"timestamp": datetime.now().isoformat(),
|
|
101
106
|
"ok": not any(f["severity"] == "ERROR" for f in findings),
|
|
102
107
|
"integrity": integrity,
|
|
103
|
-
"
|
|
108
|
+
"server_running": nexo_server_running,
|
|
104
109
|
"evolution_enabled": evolution_enabled,
|
|
105
110
|
"restore_count_current_hour": restore_count,
|
|
106
111
|
"findings": findings,
|
|
@@ -13,7 +13,7 @@ set -uo pipefail
|
|
|
13
13
|
HOME_DIR="$HOME"
|
|
14
14
|
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
15
15
|
NEXO_DIR="$NEXO_HOME"
|
|
16
|
-
CORTEX_DIR="$NEXO_HOME/
|
|
16
|
+
CORTEX_DIR="$NEXO_HOME/brain"
|
|
17
17
|
OPS_DIR="$NEXO_HOME/operations"
|
|
18
18
|
LOG_DIR="$NEXO_HOME/logs"
|
|
19
19
|
LOG="$LOG_DIR/watchdog.log"
|
|
@@ -49,12 +49,12 @@ MONITORS=(
|
|
|
49
49
|
"Cognitive Decay|com.nexo.cognitive-decay|$NEXO_HOME/logs/cognitive-decay-stdout.log|$NEXO_HOME/logs/cognitive-decay-stderr.log|90000||Daily 3:00 AM|core"
|
|
50
50
|
"Evolution|com.nexo.evolution|$NEXO_HOME/logs/evolution-stdout.log|$NEXO_HOME/logs/evolution-stderr.log|0||Weekly Sun 3:00 AM|core"
|
|
51
51
|
"GitHub Monitor|com.nexo.github-monitor|$NEXO_HOME/logs/github-monitor-stdout.log|$NEXO_HOME/logs/github-monitor-stderr.log|90000||Daily 8:00 AM|core"
|
|
52
|
-
"Immune|com.nexo.immune|$NEXO_HOME/
|
|
52
|
+
"Immune|com.nexo.immune|$NEXO_HOME/logs/immune-stdout.log|$NEXO_HOME/logs/immune-stderr.log|3600||Every 30 min|core"
|
|
53
53
|
"Postmortem|com.nexo.postmortem|$NEXO_HOME/logs/postmortem-stdout.log|$NEXO_HOME/logs/postmortem-stderr.log|90000||Daily 23:30|core"
|
|
54
54
|
"Prevent Sleep|com.nexo.prevent-sleep|||0|caffeinate|KeepAlive|core"
|
|
55
55
|
"Self Audit|com.nexo.self-audit|$NEXO_HOME/logs/self-audit-stdout.log|$NEXO_HOME/logs/self-audit-stderr.log|90000||Daily 7:00 AM|core"
|
|
56
|
-
"Sleep|com.nexo.sleep|$NEXO_HOME/
|
|
57
|
-
"Synthesis|com.nexo.synthesis|$NEXO_HOME/
|
|
56
|
+
"Sleep|com.nexo.sleep|$NEXO_HOME/logs/sleep-stdout.log|$NEXO_HOME/logs/sleep-stderr.log|90000||Daily 4:00 AM|core"
|
|
57
|
+
"Synthesis|com.nexo.synthesis|$NEXO_HOME/logs/synthesis-stdout.log|$NEXO_HOME/logs/synthesis-stderr.log|10800||Every 2 hours|core"
|
|
58
58
|
"Deep Sleep|com.nexo.deep-sleep|$NEXO_HOME/logs/deep-sleep-stdout.log|$NEXO_HOME/logs/deep-sleep-stderr.log|90000||Daily 4:30 AM|core"
|
|
59
59
|
"Followup Hygiene|com.nexo.followup-hygiene|$NEXO_HOME/logs/followup-hygiene-stdout.log|$NEXO_HOME/logs/followup-hygiene-stderr.log|604800||Weekly Sun 5:00 AM|core"
|
|
60
60
|
# Add your own personal monitors below (type "personal"):
|
|
@@ -76,11 +76,13 @@ ERROR_PATTERNS="Traceback|Error:|CRITICAL|FATAL|ModuleNotFoundError|PermissionEr
|
|
|
76
76
|
UID_NUM=$(id -u)
|
|
77
77
|
REPAIR_LOG="$LOG_DIR/watchdog-repairs.log"
|
|
78
78
|
TOTAL_HEALED=0
|
|
79
|
+
IS_MACOS=false
|
|
80
|
+
[ "$(uname)" = "Darwin" ] && IS_MACOS=true
|
|
79
81
|
|
|
80
82
|
log_repair() { echo "[$TS] REPAIR: $1" >> "$REPAIR_LOG"; log "REPAIR: $1"; }
|
|
81
83
|
|
|
82
84
|
is_loaded() {
|
|
83
|
-
launchctl list "$1" &>/dev/null
|
|
85
|
+
$IS_MACOS && launchctl list "$1" &>/dev/null
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
# ============================================================================
|
|
@@ -88,6 +90,7 @@ is_loaded() {
|
|
|
88
90
|
# ============================================================================
|
|
89
91
|
|
|
90
92
|
try_repair_launchagent() {
|
|
93
|
+
$IS_MACOS || return 1
|
|
91
94
|
local plist_id="$1"
|
|
92
95
|
local proc_grep="$2"
|
|
93
96
|
local plist_file="$HOME_DIR/Library/LaunchAgents/${plist_id}.plist"
|
|
@@ -134,6 +137,7 @@ try_repair_cron() {
|
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
try_reexecute_missed_cron() {
|
|
140
|
+
$IS_MACOS || return 1
|
|
137
141
|
# Re-execute a cron that missed its scheduled run
|
|
138
142
|
# Extracts ProgramArguments from the plist and runs them
|
|
139
143
|
local plist_id="$1"
|
|
@@ -184,6 +188,7 @@ except:
|
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
try_verify_repair() {
|
|
191
|
+
$IS_MACOS || return 1
|
|
187
192
|
# After Level 2 repair, wait and verify the service is healthy
|
|
188
193
|
local plist_id="$1"
|
|
189
194
|
local log_stdout="$2"
|
|
@@ -236,7 +241,7 @@ try_repair_backup() {
|
|
|
236
241
|
local newest
|
|
237
242
|
newest=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
|
|
238
243
|
if [ -n "$newest" ]; then
|
|
239
|
-
local age=$(( TS_EPOCH - $(stat -f %m "$newest") ))
|
|
244
|
+
if $IS_MACOS; then local age=$(( TS_EPOCH - $(stat -f %m "$newest") )); else local age=$(( TS_EPOCH - $(stat -c %Y "$newest") )); fi
|
|
240
245
|
if [ "$age" -lt 60 ]; then
|
|
241
246
|
log_repair "backup_cron.sh: ran successfully, fresh backup created"
|
|
242
247
|
return 0
|
|
@@ -249,7 +254,11 @@ try_repair_backup() {
|
|
|
249
254
|
file_age() {
|
|
250
255
|
if [ -f "$1" ]; then
|
|
251
256
|
local mod_epoch
|
|
252
|
-
|
|
257
|
+
if $IS_MACOS; then
|
|
258
|
+
mod_epoch=$(stat -f %m "$1" 2>/dev/null || echo 0)
|
|
259
|
+
else
|
|
260
|
+
mod_epoch=$(stat -c %Y "$1" 2>/dev/null || echo 0)
|
|
261
|
+
fi
|
|
253
262
|
echo $(( TS_EPOCH - mod_epoch ))
|
|
254
263
|
else
|
|
255
264
|
echo 999999
|
|
@@ -511,7 +520,7 @@ done
|
|
|
511
520
|
# --- SQLite integrity ---
|
|
512
521
|
SQLITE_STATUS="PASS"
|
|
513
522
|
SQLITE_DETAIL=""
|
|
514
|
-
INTEGRITY=$(sqlite3 "$NEXO_DIR/nexo.db" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
|
|
523
|
+
INTEGRITY=$(sqlite3 "$NEXO_DIR/data/nexo.db" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
|
|
515
524
|
if [ "$INTEGRITY" != "ok" ]; then
|
|
516
525
|
SQLITE_STATUS="FAIL"
|
|
517
526
|
SQLITE_DETAIL="Integrity check: $INTEGRITY"
|
|
@@ -519,7 +528,7 @@ if [ "$INTEGRITY" != "ok" ]; then
|
|
|
519
528
|
TOTAL_FAIL=$((TOTAL_FAIL + 1))
|
|
520
529
|
LATEST_BACKUP=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
|
|
521
530
|
if [ -n "$LATEST_BACKUP" ]; then
|
|
522
|
-
cp "$LATEST_BACKUP" "$NEXO_DIR/nexo.db"
|
|
531
|
+
cp "$LATEST_BACKUP" "$NEXO_DIR/data/nexo.db"
|
|
523
532
|
log "RESTORED from $LATEST_BACKUP"
|
|
524
533
|
SQLITE_DETAIL="${SQLITE_DETAIL}. Restored from backup."
|
|
525
534
|
fi
|
|
@@ -576,7 +585,7 @@ BACKUP_STATUS="PASS"
|
|
|
576
585
|
BACKUP_DETAIL=""
|
|
577
586
|
LATEST_BACKUP=$(ls -t "$NEXO_DIR/backups/nexo-"*.db 2>/dev/null | head -1)
|
|
578
587
|
if [ -n "$LATEST_BACKUP" ]; then
|
|
579
|
-
BACKUP_AGE=$(( TS_EPOCH - $(stat -f %m "$LATEST_BACKUP") ))
|
|
588
|
+
if $IS_MACOS; then BACKUP_AGE=$(( TS_EPOCH - $(stat -f %m "$LATEST_BACKUP") )); else BACKUP_AGE=$(( TS_EPOCH - $(stat -c %Y "$LATEST_BACKUP") )); fi
|
|
580
589
|
BACKUP_AGE_STR=$(format_age "$BACKUP_AGE")
|
|
581
590
|
if [ "$BACKUP_AGE" -gt 7200 ]; then
|
|
582
591
|
# AUTO-REPAIR: run backup now
|
|
@@ -603,7 +612,7 @@ fi
|
|
|
603
612
|
# --- Cognitive DB check ---
|
|
604
613
|
COG_STATUS="PASS"
|
|
605
614
|
COG_DETAIL=""
|
|
606
|
-
COG_DB="$NEXO_DIR/cognitive.db"
|
|
615
|
+
COG_DB="$NEXO_DIR/data/cognitive.db"
|
|
607
616
|
if [ -f "$COG_DB" ]; then
|
|
608
617
|
COG_INT=$(sqlite3 "$COG_DB" "PRAGMA integrity_check;" 2>/dev/null || echo "CORRUPT")
|
|
609
618
|
if [ "$COG_INT" != "ok" ]; then
|
|
@@ -724,7 +733,7 @@ if [ "$TOTAL_FAIL" -gt 0 ]; then
|
|
|
724
733
|
LOCK_AGE=999999
|
|
725
734
|
SKIP_REPAIR=false
|
|
726
735
|
if [ -f "$REPAIR_LOCK" ]; then
|
|
727
|
-
LOCK_AGE=$(( TS_EPOCH - $(stat -f %m "$REPAIR_LOCK" 2>/dev/null || echo 0) ))
|
|
736
|
+
if $IS_MACOS; then LOCK_AGE=$(( TS_EPOCH - $(stat -f %m "$REPAIR_LOCK" 2>/dev/null || echo 0) )); else LOCK_AGE=$(( TS_EPOCH - $(stat -c %Y "$REPAIR_LOCK" 2>/dev/null || echo 0) )); fi
|
|
728
737
|
if [ "$LOCK_AGE" -lt "$REPAIR_COOLDOWN" ]; then
|
|
729
738
|
log "NEXO repair skipped: cooldown (${LOCK_AGE}s < ${REPAIR_COOLDOWN}s)"
|
|
730
739
|
SKIP_REPAIR=true
|
|
@@ -773,7 +782,7 @@ ${STDOUT_TAIL}
|
|
|
773
782
|
if $HAS_CORE_FAILS && [ -n "$NEXO_PUBLIC_REPO" ] && [ -d "$NEXO_PUBLIC_REPO/.git" ]; then
|
|
774
783
|
PROPAGATE_BLOCK="
|
|
775
784
|
PROPAGATION (for [core] fixes ONLY):
|
|
776
|
-
If your fix modifies a file under $NEXO_HOME/ (server.py, db
|
|
785
|
+
If your fix modifies a file under $NEXO_HOME/ (server.py, db/, plugins/, scripts/):
|
|
777
786
|
1. Commit the fix locally with a descriptive message
|
|
778
787
|
2. Copy the changed files (sanitized — no personal data) to $NEXO_PUBLIC_REPO/src/
|
|
779
788
|
3. Bump patch version in $NEXO_PUBLIC_REPO/package.json
|