nexo-brain 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/bin/nexo-brain.js +680 -381
- package/package.json +4 -1
- package/scripts/migrate-to-unified.sh +813 -0
- package/scripts/migrate-v1.7-to-v1.8.py +214 -0
- package/scripts/pre-commit-check.sh +1 -1
- package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-314.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-310.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-310.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/server.cpython-310.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions.py +1 -1
- package/src/auto_update.py +634 -0
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core.py +7 -3
- package/src/cognitive/_decay.py +1 -1
- package/src/cognitive/_search.py +1 -0
- package/src/cognitive/_trust.py +3 -3
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app.py +8 -2
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core.py +5 -1
- package/src/db/_episodic.py +1 -3
- package/src/db/_reminders.py +36 -1
- package/src/db/_schema.py +31 -0
- package/src/evolution_cycle.py +33 -11
- package/src/hooks/__pycache__/auto_capture.cpython-310.pyc +0 -0
- package/src/hooks/auto_capture.py +1 -1
- package/src/hooks/capture-tool-logs.sh +76 -0
- package/src/hooks/inbox-hook.sh +2 -1
- package/src/hooks/post-compact.sh +2 -1
- package/src/hooks/pre-compact.sh +104 -2
- package/src/hooks/session-start.sh +6 -2
- package/src/hooks/session-stop.sh +2 -1
- package/src/kg_populate.py +4 -1
- package/src/migrate_embeddings.py +4 -1
- package/src/plugin_loader.py +100 -34
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/agents.py +2 -2
- package/src/plugins/backup.py +5 -4
- package/src/plugins/core_rules.py +5 -1
- package/src/plugins/episodic_memory.py +14 -5
- package/src/plugins/evolution.py +6 -2
- package/src/plugins/guard.py +20 -11
- package/src/plugins/update.py +238 -0
- package/src/requirements.txt +12 -0
- package/src/rules/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/check-context.py +9 -1
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-310.pyc +0 -0
- package/src/scripts/deep-sleep/apply_findings.py +3 -3
- package/src/scripts/nexo-auto-update.py +4 -211
- package/src/scripts/nexo-backup.sh +5 -13
- package/src/scripts/nexo-brain-activation.sh +26 -26
- package/src/scripts/nexo-catchup.py +36 -22
- package/src/scripts/nexo-cognitive-decay.py +7 -3
- package/src/scripts/nexo-daily-self-audit.py +30 -10
- package/src/scripts/nexo-evolution-run.py +35 -10
- package/src/scripts/nexo-followup-hygiene.py +2 -2
- package/src/scripts/nexo-github-monitor.py +11 -4
- package/src/scripts/nexo-immune.py +17 -2
- package/src/scripts/nexo-inbox-hook.sh +2 -1
- package/src/scripts/nexo-install.py +4 -225
- package/src/scripts/nexo-learning-housekeep.py +7 -3
- package/src/scripts/nexo-learning-validator.py +10 -2
- package/src/scripts/nexo-migrate.py +9 -3
- package/src/scripts/nexo-postmortem-consolidator.py +22 -4
- package/src/scripts/nexo-pre-commit.py +3 -1
- package/src/scripts/nexo-prevent-sleep.sh +29 -0
- package/src/scripts/nexo-proactive-dashboard.py +4 -4
- package/src/scripts/nexo-runtime-preflight.py +59 -55
- package/src/scripts/nexo-send-email.py +1 -1
- package/src/scripts/nexo-send-reply.py +3 -1
- package/src/scripts/nexo-sleep.py +19 -5
- package/src/scripts/nexo-snapshot-restore.sh +2 -1
- package/src/scripts/nexo-synthesis.py +17 -2
- package/src/scripts/nexo-tcc-approve.sh +79 -0
- package/src/scripts/nexo-update.sh +161 -0
- package/src/scripts/nexo-watchdog-smoke.py +18 -13
- package/src/scripts/nexo-watchdog.sh +22 -13
- package/src/server.py +77 -28
- package/src/storage_router.py +6 -2
- package/src/tools_learnings.py +6 -6
- package/src/tools_reminders_crud.py +10 -8
- package/src/tools_sessions.py +9 -4
- package/templates/CLAUDE.md.template +14 -80
- package/templates/launchagents/README.md +7 -7
- package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
- package/templates/launchagents/com.nexo.catchup.plist +4 -0
- package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
- package/templates/launchagents/com.nexo.dashboard.plist +5 -1
- package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
- package/templates/launchagents/com.nexo.evolution.plist +4 -0
- package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
- package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
- package/templates/launchagents/com.nexo.immune.plist +4 -0
- package/templates/launchagents/com.nexo.postmortem.plist +4 -0
- package/templates/launchagents/com.nexo.self-audit.plist +4 -0
- package/templates/launchagents/com.nexo.synthesis.plist +4 -0
- package/templates/launchagents/com.nexo.watchdog.plist +4 -0
- package/templates/openclaw.json +1 -1
- package/tests/__pycache__/__init__.cpython-310.pyc +0 -0
- package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-310.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-310.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/conftest.py +2 -2
- package/tests/test_cognitive.py +7 -6
- package/tests/test_migrations.py +26 -0
package/src/cognitive/_decay.py
CHANGED
|
@@ -254,7 +254,7 @@ def dream_cycle(max_insights: int = 50) -> dict:
|
|
|
254
254
|
})
|
|
255
255
|
|
|
256
256
|
if len(recent_memories) < 2:
|
|
257
|
-
return {"insights_created": 0, "insights": [], "memories_scanned": len(recent_memories)}
|
|
257
|
+
return {"insights_created": 0, "insights": [], "memories_scanned": len(recent_memories), "candidates_found": 0}
|
|
258
258
|
|
|
259
259
|
# 2. Get already-dreamed pairs to skip
|
|
260
260
|
dreamed = set()
|
package/src/cognitive/_search.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""NEXO Cognitive — Search, retrieval, ranking."""
|
|
2
2
|
import math
|
|
3
|
+
import sqlite3
|
|
3
4
|
import numpy as np
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array, _array_to_blob, _get_model, _get_reranker, rerank_results, EMBEDDING_DIM
|
package/src/cognitive/_trust.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""NEXO Cognitive — Trust scoring, sentiment, dissonance."""
|
|
2
2
|
import re
|
|
3
3
|
import numpy as np
|
|
4
|
-
from datetime import datetime
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
5
|
from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array
|
|
6
6
|
from cognitive._core import POSITIVE_SIGNALS, NEGATIVE_SIGNALS, URGENCY_SIGNALS
|
|
7
7
|
|
|
@@ -314,13 +314,13 @@ def detect_sentiment(text: str) -> dict:
|
|
|
314
314
|
sentiment = "negative"
|
|
315
315
|
intensity = min(1.0, 0.3 + neg_score * 0.15)
|
|
316
316
|
if intensity > 0.7:
|
|
317
|
-
guidance = "MODE: Ultra-
|
|
317
|
+
guidance = "MODE: Ultra-concise. Zero explanations. Solve and show result."
|
|
318
318
|
else:
|
|
319
319
|
guidance = "MODE: Concise. Less context, more direct action."
|
|
320
320
|
elif pos_score > neg_score and pos_score >= 1:
|
|
321
321
|
sentiment = "positive"
|
|
322
322
|
intensity = min(1.0, 0.3 + pos_score * 0.15)
|
|
323
|
-
guidance = "MODE: Normal.
|
|
323
|
+
guidance = "MODE: Normal. Good time to suggest backlog ideas or improvements."
|
|
324
324
|
elif urgency_hits:
|
|
325
325
|
sentiment = "urgent"
|
|
326
326
|
intensity = 0.8
|
|
Binary file
|
|
Binary file
|
package/src/dashboard/app.py
CHANGED
|
@@ -10,6 +10,7 @@ Usage:
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
|
+
import platform
|
|
13
14
|
import subprocess
|
|
14
15
|
import sys
|
|
15
16
|
import time
|
|
@@ -22,7 +23,7 @@ from fastapi.responses import HTMLResponse, JSONResponse
|
|
|
22
23
|
from fastapi.staticfiles import StaticFiles
|
|
23
24
|
from pydantic import BaseModel
|
|
24
25
|
|
|
25
|
-
# Add parent dir to path so we can import
|
|
26
|
+
# Add parent dir to path so we can import NEXO modules
|
|
26
27
|
_PARENT = str(Path(__file__).resolve().parent.parent)
|
|
27
28
|
if _PARENT not in sys.path:
|
|
28
29
|
sys.path.insert(0, _PARENT)
|
|
@@ -63,7 +64,7 @@ async def create_tables():
|
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
# ---------------------------------------------------------------------------
|
|
66
|
-
# Lazy imports — modules live in the parent
|
|
67
|
+
# Lazy imports — modules live in the parent source directory
|
|
67
68
|
# ---------------------------------------------------------------------------
|
|
68
69
|
|
|
69
70
|
def _cognitive():
|
|
@@ -602,6 +603,11 @@ async def api_ops_execute(fid: str):
|
|
|
602
603
|
return JSONResponse({"error": f"Followup {fid} not found"}, status_code=404)
|
|
603
604
|
item = dict(row)
|
|
604
605
|
description = item["description"].replace('"', '\\"').replace("'", "\\'")
|
|
606
|
+
if platform.system() != "Darwin":
|
|
607
|
+
return JSONResponse(
|
|
608
|
+
{"error": "This operation requires macOS (uses osascript to open Terminal)"},
|
|
609
|
+
status_code=501,
|
|
610
|
+
)
|
|
605
611
|
script = f'tell application "Terminal" to do script "claude \\"NEXO: execute followup #{fid} — {description}\\""'
|
|
606
612
|
subprocess.Popen(["osascript", "-e", script])
|
|
607
613
|
return {"success": True, "followup_id": fid}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/db/_core.py
CHANGED
|
@@ -9,11 +9,15 @@ import datetime
|
|
|
9
9
|
import pathlib
|
|
10
10
|
import threading
|
|
11
11
|
|
|
12
|
+
NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
13
|
+
_data_dir = os.path.join(NEXO_HOME, "data")
|
|
14
|
+
os.makedirs(_data_dir, exist_ok=True)
|
|
15
|
+
|
|
12
16
|
DB_PATH = os.environ.get(
|
|
13
17
|
"NEXO_TEST_DB",
|
|
14
18
|
os.environ.get(
|
|
15
19
|
"NEXO_DB",
|
|
16
|
-
os.path.join(
|
|
20
|
+
os.path.join(_data_dir, "nexo.db"),
|
|
17
21
|
),
|
|
18
22
|
)
|
|
19
23
|
|
package/src/db/_episodic.py
CHANGED
|
@@ -148,9 +148,7 @@ def update_change_commit(id: int, commit_ref: str) -> dict:
|
|
|
148
148
|
fts_upsert("change", str(id), r.get("files",""), body, "change_log", commit=False)
|
|
149
149
|
|
|
150
150
|
# Auto-resolve followups that match this change
|
|
151
|
-
|
|
152
|
-
if resolved:
|
|
153
|
-
r["auto_resolved_followups"] = resolved
|
|
151
|
+
r["_auto_resolved"] = auto_resolve_followups(r)
|
|
154
152
|
return r
|
|
155
153
|
|
|
156
154
|
|
package/src/db/_reminders.py
CHANGED
|
@@ -134,11 +134,22 @@ def create_followup(id: str, description: str, date: str = None,
|
|
|
134
134
|
reasoning: str = '', recurrence: str = None) -> dict:
|
|
135
135
|
"""Create a new followup with optional reasoning and recurrence.
|
|
136
136
|
|
|
137
|
+
Checks for similar open followups before creating. If a match is found,
|
|
138
|
+
returns a warning with the existing followup ID (still creates the new one).
|
|
139
|
+
|
|
137
140
|
recurrence format: 'weekly:monday', 'monthly:1', 'monthly:10', 'quarterly', etc.
|
|
138
141
|
When a recurring followup is completed, a new one is auto-created with the next date.
|
|
139
142
|
"""
|
|
140
143
|
conn = get_db()
|
|
141
144
|
now = now_epoch()
|
|
145
|
+
|
|
146
|
+
# Anti-duplicate check
|
|
147
|
+
similar = find_similar_followups(description)
|
|
148
|
+
warning = ""
|
|
149
|
+
if similar:
|
|
150
|
+
ids = ", ".join(s["id"] for s in similar[:3])
|
|
151
|
+
warning = f" ⚠ SIMILAR FOLLOWUPS EXIST: {ids} (scores: {', '.join(str(s['_similarity']) for s in similar[:3])}). Consider updating instead."
|
|
152
|
+
|
|
142
153
|
try:
|
|
143
154
|
conn.execute(
|
|
144
155
|
"INSERT INTO followups (id, date, description, verification, status, reasoning, recurrence, created_at, updated_at) "
|
|
@@ -150,7 +161,10 @@ def create_followup(id: str, description: str, date: str = None,
|
|
|
150
161
|
except sqlite3.IntegrityError:
|
|
151
162
|
return {"error": f"Followup {id} already exists. Use update instead."}
|
|
152
163
|
row = conn.execute("SELECT * FROM followups WHERE id = ?", (id,)).fetchone()
|
|
153
|
-
|
|
164
|
+
result = dict(row)
|
|
165
|
+
if warning:
|
|
166
|
+
result["warning"] = warning
|
|
167
|
+
return result
|
|
154
168
|
|
|
155
169
|
|
|
156
170
|
def update_followup(id: str, **kwargs) -> dict:
|
|
@@ -245,6 +259,18 @@ def complete_followup(id: str, result: str = '') -> dict:
|
|
|
245
259
|
archived_id = f"{id}-{today}"
|
|
246
260
|
conn.execute("UPDATE followups SET id = ? WHERE id = ?", (archived_id, id))
|
|
247
261
|
conn.commit()
|
|
262
|
+
|
|
263
|
+
# Fix FTS: remove old entry for original ID, add entry for archived ID
|
|
264
|
+
conn.execute("DELETE FROM unified_search WHERE source = 'followup' AND source_id = ?", (id,))
|
|
265
|
+
archived_row = conn.execute("SELECT * FROM followups WHERE id = ?", (archived_id,)).fetchone()
|
|
266
|
+
if archived_row:
|
|
267
|
+
fts_upsert(
|
|
268
|
+
"followup", archived_id, archived_id,
|
|
269
|
+
f"{archived_row['description']} {archived_row['verification'] or ''} {archived_row['reasoning'] or ''}",
|
|
270
|
+
"followup", commit=False,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# create_followup handles its own FTS entry for the new recurring ID
|
|
248
274
|
create_followup(
|
|
249
275
|
id=id,
|
|
250
276
|
description=row["description"],
|
|
@@ -254,6 +280,15 @@ def complete_followup(id: str, result: str = '') -> dict:
|
|
|
254
280
|
recurrence=recurrence,
|
|
255
281
|
)
|
|
256
282
|
|
|
283
|
+
# Return accurate result: the completed one is now archived_id, not id
|
|
284
|
+
return {
|
|
285
|
+
"id": archived_id,
|
|
286
|
+
"status": "COMPLETED",
|
|
287
|
+
"recurrence": recurrence,
|
|
288
|
+
"next_id": id,
|
|
289
|
+
"next_date": next_date,
|
|
290
|
+
}
|
|
291
|
+
|
|
257
292
|
return update_result
|
|
258
293
|
|
|
259
294
|
|
package/src/db/_schema.py
CHANGED
|
@@ -265,6 +265,36 @@ def _m14_learnings_priority_weight(conn):
|
|
|
265
265
|
_migrate_add_column(conn, "followups", "priority", "TEXT DEFAULT 'medium'")
|
|
266
266
|
|
|
267
267
|
|
|
268
|
+
def _m15_core_rules_tables(conn):
|
|
269
|
+
"""Core rules and version tracking tables for the core_rules plugin."""
|
|
270
|
+
conn.execute("""
|
|
271
|
+
CREATE TABLE IF NOT EXISTS core_rules (
|
|
272
|
+
id TEXT PRIMARY KEY,
|
|
273
|
+
category TEXT NOT NULL,
|
|
274
|
+
rule TEXT NOT NULL,
|
|
275
|
+
why TEXT NOT NULL,
|
|
276
|
+
importance INTEGER NOT NULL DEFAULT 3,
|
|
277
|
+
type TEXT NOT NULL DEFAULT 'advisory',
|
|
278
|
+
added_in TEXT DEFAULT '',
|
|
279
|
+
removed_in TEXT DEFAULT NULL,
|
|
280
|
+
is_active INTEGER NOT NULL DEFAULT 1
|
|
281
|
+
)
|
|
282
|
+
""")
|
|
283
|
+
conn.execute("""
|
|
284
|
+
CREATE TABLE IF NOT EXISTS core_rules_version (
|
|
285
|
+
id INTEGER PRIMARY KEY,
|
|
286
|
+
version TEXT NOT NULL DEFAULT '0.0.0',
|
|
287
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
288
|
+
)
|
|
289
|
+
""")
|
|
290
|
+
# Seed the version row so UPDATE statements in the plugin always find it
|
|
291
|
+
conn.execute(
|
|
292
|
+
"INSERT OR IGNORE INTO core_rules_version (id, version) VALUES (1, '0.0.0')"
|
|
293
|
+
)
|
|
294
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_category ON core_rules(category)")
|
|
295
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_active ON core_rules(is_active)")
|
|
296
|
+
|
|
297
|
+
|
|
268
298
|
# Migration registry — APPEND ONLY, never reorder or delete
|
|
269
299
|
MIGRATIONS = [
|
|
270
300
|
(1, "learnings_columns", _m1_learnings_columns),
|
|
@@ -281,6 +311,7 @@ MIGRATIONS = [
|
|
|
281
311
|
(12, "session_checkpoints", _m12_session_checkpoints),
|
|
282
312
|
(13, "claude_session_id", _m13_claude_session_id),
|
|
283
313
|
(14, "learnings_priority_weight", _m14_learnings_priority_weight),
|
|
314
|
+
(15, "core_rules_tables", _m15_core_rules_tables),
|
|
284
315
|
]
|
|
285
316
|
|
|
286
317
|
|
package/src/evolution_cycle.py
CHANGED
|
@@ -14,14 +14,22 @@ import time
|
|
|
14
14
|
from datetime import datetime, date, timedelta
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
SANDBOX_DIR =
|
|
21
|
-
SNAPSHOTS_DIR =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
18
|
+
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(NEXO_HOME)))
|
|
19
|
+
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
20
|
+
SANDBOX_DIR = NEXO_HOME / "sandbox" / "workspace"
|
|
21
|
+
SNAPSHOTS_DIR = NEXO_HOME / "snapshots"
|
|
22
|
+
RESTORE_LOG = NEXO_HOME / "logs" / "snapshot-restores.log"
|
|
23
|
+
|
|
24
|
+
# Evolution config: brain/ (canonical) > cortex/ (legacy) > NEXO_CODE (dev)
|
|
25
|
+
def _resolve_evolution_file(name: str) -> Path:
|
|
26
|
+
for candidate in [NEXO_HOME / "brain" / name, NEXO_HOME / "cortex" / name, NEXO_CODE / name]:
|
|
27
|
+
if candidate.exists():
|
|
28
|
+
return candidate
|
|
29
|
+
return NEXO_HOME / "brain" / name # default canonical path
|
|
30
|
+
|
|
31
|
+
OBJECTIVE_FILE = _resolve_evolution_file("evolution-objective.json")
|
|
32
|
+
PROMPT_FILE = _resolve_evolution_file("evolution-prompt.md")
|
|
25
33
|
|
|
26
34
|
MAX_SNAPSHOTS = 8
|
|
27
35
|
|
|
@@ -105,6 +113,8 @@ def create_snapshot(files_to_backup: list) -> str:
|
|
|
105
113
|
rel = str(fp).replace(str(Path.home()) + "/", "")
|
|
106
114
|
dest = files_dir / rel
|
|
107
115
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
if os.path.abspath(str(fp)) == os.path.abspath(str(dest)):
|
|
117
|
+
continue # Skip: source and destination are the same file
|
|
108
118
|
shutil.copy2(fp, dest)
|
|
109
119
|
manifest["files"].append(rel)
|
|
110
120
|
|
|
@@ -144,9 +154,21 @@ def dry_run_restore_test() -> bool:
|
|
|
144
154
|
|
|
145
155
|
test_file.write_text("modified_content")
|
|
146
156
|
|
|
157
|
+
# Find restore script: NEXO_CODE/scripts/ first, then NEXO_HOME/scripts/
|
|
158
|
+
_nexo_code = Path(os.environ.get("NEXO_CODE", ""))
|
|
159
|
+
restore_script = None
|
|
160
|
+
for candidate in [_nexo_code / "scripts" / "nexo-snapshot-restore.sh",
|
|
161
|
+
NEXO_HOME / "scripts" / "nexo-snapshot-restore.sh"]:
|
|
162
|
+
if candidate.exists():
|
|
163
|
+
restore_script = candidate
|
|
164
|
+
break
|
|
165
|
+
if not restore_script:
|
|
166
|
+
test_file.unlink(missing_ok=True)
|
|
167
|
+
return False # No restore script available
|
|
168
|
+
|
|
147
169
|
try:
|
|
148
170
|
subprocess.run(
|
|
149
|
-
[str(
|
|
171
|
+
[str(restore_script), snap_dir],
|
|
150
172
|
capture_output=True, timeout=10, check=True
|
|
151
173
|
)
|
|
152
174
|
content = test_file.read_text()
|
|
@@ -200,7 +222,7 @@ INVESTIGATE using these tools:
|
|
|
200
222
|
4. Read ~/.nexo/coordination/postmortem-daily.md — self-critique patterns
|
|
201
223
|
5. Read ~/.nexo/logs/self-audit-summary.json — system health
|
|
202
224
|
6. Glob ~/.nexo/scripts/*.py — existing scripts
|
|
203
|
-
7. Glob ~/.nexo/
|
|
225
|
+
7. Glob ~/.nexo/plugins/*.py — existing plugins
|
|
204
226
|
|
|
205
227
|
LOOK FOR:
|
|
206
228
|
- Repeated errors that guard isn't preventing
|
|
@@ -210,7 +232,7 @@ LOOK FOR:
|
|
|
210
232
|
- Patterns in self-critique that suggest systemic issues
|
|
211
233
|
|
|
212
234
|
SAFETY:
|
|
213
|
-
- Safe zones for auto changes: ~/.nexo/scripts/, ~/.nexo/
|
|
235
|
+
- Safe zones for auto changes: ~/.nexo/scripts/, ~/.nexo/plugins/, ~/.nexo/brain/
|
|
214
236
|
- IMMUTABLE files (never touch): db.py, server.py, plugin_loader.py, cognitive.py, CLAUDE.md
|
|
215
237
|
- Every change needs: what file, what to change, why, risk, how to verify
|
|
216
238
|
|
|
Binary file
|
|
@@ -40,6 +40,82 @@ record = {
|
|
|
40
40
|
print(json.dumps(record))
|
|
41
41
|
" >> "$LOG_FILE" 2>/dev/null
|
|
42
42
|
|
|
43
|
+
# ── Layer 1: Auto-diary every 10 tool calls ─────────────────────────
|
|
44
|
+
COUNTER_FILE="$NEXO_HOME/operations/.tool-call-count"
|
|
45
|
+
NEXO_DB="$NEXO_HOME/data/nexo.db"
|
|
46
|
+
|
|
47
|
+
# Increment counter (atomic: read+write in one step)
|
|
48
|
+
COUNT=1
|
|
49
|
+
if [ -f "$COUNTER_FILE" ]; then
|
|
50
|
+
COUNT=$(( $(cat "$COUNTER_FILE" 2>/dev/null || echo 0) + 1 ))
|
|
51
|
+
fi
|
|
52
|
+
echo "$COUNT" > "$COUNTER_FILE"
|
|
53
|
+
|
|
54
|
+
# Every 10 tool calls, write a mechanical diary draft to SQLite
|
|
55
|
+
if [ $(( COUNT % 10 )) -eq 0 ] && [ -f "$NEXO_DB" ]; then
|
|
56
|
+
python3 -c "
|
|
57
|
+
import json, sqlite3, os, sys
|
|
58
|
+
from datetime import datetime
|
|
59
|
+
|
|
60
|
+
db_path = '$NEXO_DB'
|
|
61
|
+
log_file = '$LOG_FILE'
|
|
62
|
+
count = $COUNT
|
|
63
|
+
|
|
64
|
+
# Read last 10 tool calls from today's log
|
|
65
|
+
entries = []
|
|
66
|
+
if os.path.isfile(log_file):
|
|
67
|
+
with open(log_file, 'r') as f:
|
|
68
|
+
lines = f.readlines()
|
|
69
|
+
for line in lines[-10:]:
|
|
70
|
+
try:
|
|
71
|
+
e = json.loads(line.strip())
|
|
72
|
+
name = e.get('tool_name', '?')
|
|
73
|
+
inp = e.get('tool_input', {})
|
|
74
|
+
# Brief args: first key's value, truncated
|
|
75
|
+
brief = ''
|
|
76
|
+
if isinstance(inp, dict):
|
|
77
|
+
for k, v in list(inp.items())[:1]:
|
|
78
|
+
brief = str(v)[:60]
|
|
79
|
+
entries.append(f'{name}({brief})')
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if not entries:
|
|
84
|
+
sys.exit(0)
|
|
85
|
+
|
|
86
|
+
tools_summary = ', '.join(entries[-10:])
|
|
87
|
+
|
|
88
|
+
# Get current session and task from sessions table
|
|
89
|
+
conn = sqlite3.connect(db_path, timeout=2)
|
|
90
|
+
conn.row_factory = sqlite3.Row
|
|
91
|
+
row = conn.execute(
|
|
92
|
+
'SELECT sid, task FROM sessions ORDER BY last_update_epoch DESC LIMIT 1'
|
|
93
|
+
).fetchone()
|
|
94
|
+
if not row:
|
|
95
|
+
conn.close()
|
|
96
|
+
sys.exit(0)
|
|
97
|
+
|
|
98
|
+
sid = row['sid']
|
|
99
|
+
task = row['task'] or 'unknown'
|
|
100
|
+
|
|
101
|
+
summary = f'[AUTO-{count}] {len(entries)} tool calls: {tools_summary[:250]}. Task: {task[:100]}'
|
|
102
|
+
|
|
103
|
+
# Write to session_diary_draft (UPSERT)
|
|
104
|
+
conn.execute('''
|
|
105
|
+
INSERT INTO session_diary_draft (sid, summary_draft, tasks_seen, change_ids, decision_ids, last_context_hint, heartbeat_count, updated_at)
|
|
106
|
+
VALUES (?, ?, '[]', '[]', '[]', ?, 0, datetime('now'))
|
|
107
|
+
ON CONFLICT(sid) DO UPDATE SET
|
|
108
|
+
summary_draft = excluded.summary_draft,
|
|
109
|
+
last_context_hint = excluded.last_context_hint,
|
|
110
|
+
updated_at = datetime('now')
|
|
111
|
+
''', (sid, summary, f'auto-diary at {count} tool calls'))
|
|
112
|
+
conn.commit()
|
|
113
|
+
conn.close()
|
|
114
|
+
" 2>/dev/null &
|
|
115
|
+
# Reset counter after writing
|
|
116
|
+
echo "0" > "$COUNTER_FILE"
|
|
117
|
+
fi
|
|
118
|
+
|
|
43
119
|
# Cleanup: delete logs >= 30 days old (once daily, uses marker file)
|
|
44
120
|
CLEANUP_MARKER="$LOG_DIR/.last-cleanup"
|
|
45
121
|
if [ ! -f "$CLEANUP_MARKER" ] || [ "$(cat "$CLEANUP_MARKER" 2>/dev/null)" != "$TODAY" ]; then
|
package/src/hooks/inbox-hook.sh
CHANGED
|
@@ -27,7 +27,8 @@ echo "$NOW" > "$DEBOUNCE_FILE"
|
|
|
27
27
|
|
|
28
28
|
# 4. Find NEXO SID mapped to this Claude session_id
|
|
29
29
|
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
30
|
-
DB="$NEXO_HOME/nexo.db"
|
|
30
|
+
DB="$NEXO_HOME/data/nexo.db"
|
|
31
|
+
mkdir -p "$NEXO_HOME/data"
|
|
31
32
|
[ -f "$DB" ] || exit 0
|
|
32
33
|
|
|
33
34
|
NEXO_SID=$(sqlite3 "$DB" "SELECT sid FROM sessions WHERE claude_session_id = '${CLAUDE_SID}' AND last_update_epoch > (strftime('%s','now') - 900) ORDER BY last_update_epoch DESC LIMIT 1;" 2>/dev/null)
|
package/src/hooks/pre-compact.sh
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
8
|
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
9
|
-
NEXO_DB="$NEXO_HOME/nexo.db"
|
|
9
|
+
NEXO_DB="$NEXO_HOME/data/nexo.db"
|
|
10
|
+
mkdir -p "$NEXO_HOME/data"
|
|
10
11
|
TODAY=$(date +%Y-%m-%d)
|
|
11
12
|
LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
|
|
12
13
|
LOG_LINES=0
|
|
@@ -42,8 +43,109 @@ if [ -f "$NEXO_DB" ]; then
|
|
|
42
43
|
fi
|
|
43
44
|
fi
|
|
44
45
|
|
|
46
|
+
# ── Layer 2: Emergency auto-diary before compaction ──────────────────
|
|
47
|
+
# Write an actual session_diary entry (not draft) with mechanical summary
|
|
48
|
+
# This is the parachute — if the LLM never wrote a diary, at least this exists
|
|
49
|
+
if [ -f "$NEXO_DB" ]; then
|
|
50
|
+
python3 -c "
|
|
51
|
+
import json, sqlite3, os, sys
|
|
52
|
+
from datetime import datetime
|
|
53
|
+
|
|
54
|
+
db_path = '$NEXO_DB'
|
|
55
|
+
log_file = '$LOG_FILE'
|
|
56
|
+
|
|
57
|
+
conn = sqlite3.connect(db_path, timeout=3)
|
|
58
|
+
conn.row_factory = sqlite3.Row
|
|
59
|
+
|
|
60
|
+
# Get latest active session
|
|
61
|
+
row = conn.execute(
|
|
62
|
+
'SELECT sid, task FROM sessions ORDER BY last_update_epoch DESC LIMIT 1'
|
|
63
|
+
).fetchone()
|
|
64
|
+
if not row:
|
|
65
|
+
conn.close()
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
sid = row['sid']
|
|
69
|
+
task = row['task'] or 'unknown'
|
|
70
|
+
|
|
71
|
+
# Check if a real diary already exists for this session
|
|
72
|
+
has_diary = conn.execute(
|
|
73
|
+
'SELECT id FROM session_diary WHERE session_id = ? LIMIT 1', (sid,)
|
|
74
|
+
).fetchone()
|
|
75
|
+
if has_diary:
|
|
76
|
+
conn.close()
|
|
77
|
+
sys.exit(0) # LLM already wrote one, no need for emergency diary
|
|
78
|
+
|
|
79
|
+
# Find last diary timestamp to know where to start reading logs
|
|
80
|
+
last_diary = conn.execute(
|
|
81
|
+
'SELECT created_at FROM session_diary ORDER BY created_at DESC LIMIT 1'
|
|
82
|
+
).fetchone()
|
|
83
|
+
last_diary_ts = last_diary['created_at'] if last_diary else '1970-01-01T00:00:00Z'
|
|
84
|
+
|
|
85
|
+
# Read tool log entries since last diary
|
|
86
|
+
entries = []
|
|
87
|
+
modified_files = []
|
|
88
|
+
git_actions = []
|
|
89
|
+
if os.path.isfile(log_file):
|
|
90
|
+
with open(log_file, 'r') as f:
|
|
91
|
+
for line in f:
|
|
92
|
+
try:
|
|
93
|
+
e = json.loads(line.strip())
|
|
94
|
+
ts = e.get('timestamp', '')
|
|
95
|
+
if ts < last_diary_ts:
|
|
96
|
+
continue
|
|
97
|
+
name = e.get('tool_name', '?')
|
|
98
|
+
inp = e.get('tool_input', {}) or {}
|
|
99
|
+
brief = ''
|
|
100
|
+
if isinstance(inp, dict):
|
|
101
|
+
for k, v in list(inp.items())[:1]:
|
|
102
|
+
brief = str(v)[:80]
|
|
103
|
+
entries.append(f'{name}({brief})')
|
|
104
|
+
# Extract decisions from tool calls
|
|
105
|
+
if name in ('Edit', 'Write'):
|
|
106
|
+
fp = inp.get('file_path', inp.get('path', ''))
|
|
107
|
+
if fp:
|
|
108
|
+
modified_files.append(fp.split('/')[-1])
|
|
109
|
+
if name == 'Bash':
|
|
110
|
+
cmd = str(inp.get('command', ''))
|
|
111
|
+
if 'git commit' in cmd or 'git push' in cmd:
|
|
112
|
+
git_actions.append(cmd[:80])
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
if not entries:
|
|
117
|
+
conn.close()
|
|
118
|
+
sys.exit(0)
|
|
119
|
+
|
|
120
|
+
# Build mechanical diary
|
|
121
|
+
tools_summary = ', '.join(entries[-30:])[:500]
|
|
122
|
+
summary = f'[EMERGENCY PRE-COMPACT] {len(entries)} tool calls since last diary. Tools: {tools_summary}'
|
|
123
|
+
|
|
124
|
+
decisions = ''
|
|
125
|
+
if modified_files:
|
|
126
|
+
decisions = 'Modified: ' + ', '.join(set(modified_files))[:300]
|
|
127
|
+
if git_actions:
|
|
128
|
+
decisions += (' | Git: ' + ', '.join(git_actions))[:200]
|
|
129
|
+
if not decisions:
|
|
130
|
+
decisions = 'No file modifications detected in tool logs'
|
|
131
|
+
|
|
132
|
+
pending = f'Current task: {task[:200]}'
|
|
133
|
+
context_next = 'COMPACTION HAPPENED. Read this diary to continue. Check session_checkpoints and tool-logs for full context.'
|
|
134
|
+
|
|
135
|
+
# Write actual session_diary entry
|
|
136
|
+
conn.execute('''
|
|
137
|
+
INSERT INTO session_diary
|
|
138
|
+
(session_id, decisions, discarded, pending, context_next,
|
|
139
|
+
mental_state, domain, user_signals, summary, source)
|
|
140
|
+
VALUES (?, ?, '', ?, ?, 'auto-generated', 'auto', '', ?, 'pre-compact-hook')
|
|
141
|
+
''', (sid, decisions, pending, context_next, summary))
|
|
142
|
+
conn.commit()
|
|
143
|
+
conn.close()
|
|
144
|
+
" 2>/dev/null || true
|
|
145
|
+
fi
|
|
146
|
+
|
|
45
147
|
cat << HOOKEOF
|
|
46
148
|
{
|
|
47
|
-
"systemMessage": "CONTEXT IS ABOUT TO BE COMPRESSED.\n\nOBLIGATORY ACTIONS BEFORE COMPACTION:\n1. Save critical state via MCP: nexo_checkpoint_save with current task, active files, decisions, errors, next step, and reasoning thread.\n2. If there is work in progress without a commit, save data via nexo_entity_create, nexo_preference_set, nexo_learning_add, nexo_followup_create.\n3. PERSISTENT TOOL LOGS: ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl has ${LOG_LINES} entries.\n4. After compaction, the PostCompact hook will re-inject a Core Memory Block with the checkpoint.\n5. MCP tools (nexo_*) preserve all state — use them to recover context."
|
|
149
|
+
"systemMessage": "CONTEXT IS ABOUT TO BE COMPRESSED.\n\nOBLIGATORY ACTIONS BEFORE COMPACTION:\n1. Save critical state via MCP: nexo_checkpoint_save with current task, active files, decisions, errors, next step, and reasoning thread.\n2. If there is work in progress without a commit, save data via nexo_entity_create, nexo_preference_set, nexo_learning_add, nexo_followup_create.\n3. PERSISTENT TOOL LOGS: ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl has ${LOG_LINES} entries.\n4. After compaction, the PostCompact hook will re-inject a Core Memory Block with the checkpoint.\n5. MCP tools (nexo_*) preserve all state — use them to recover context.\n6. EMERGENCY DIARY: An automatic diary was written by the pre-compact hook. The LLM can still write a better one via nexo_session_diary_write."
|
|
48
150
|
}
|
|
49
151
|
HOOKEOF
|