nexo-brain 2.0.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 +140 -41
- package/package.json +15 -3
- 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/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/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 +16 -2
- 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/_episodic.py +1 -1
- package/src/db/_reminders.py +9 -5
- package/src/hooks/session-stop.sh +2 -1
- 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/core_rules.py +34 -17
- package/src/plugins/update.py +18 -0
- package/src/scripts/check-context.py +4 -7
- package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/apply_findings.py +512 -167
- 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-catchup.py +5 -8
- package/src/scripts/nexo-daily-self-audit.py +28 -19
- package/src/scripts/nexo-deep-sleep.sh +31 -16
- package/src/scripts/nexo-evolution-run.py +5 -20
- package/src/scripts/nexo-followup-hygiene.py +4 -2
- package/src/scripts/nexo-github-monitor.py +6 -9
- package/src/scripts/nexo-immune.py +4 -17
- package/src/scripts/nexo-learning-validator.py +0 -29
- package/src/scripts/nexo-postmortem-consolidator.py +9 -20
- package/src/scripts/nexo-proactive-dashboard.py +1 -0
- package/src/scripts/nexo-sleep.py +8 -18
- package/src/scripts/nexo-synthesis.py +8 -19
- package/src/tools_menu.py +1 -1
- package/src/tools_sessions.py +67 -0
- package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.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__/claim_graph.cpython-314.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__/kg_populate.cpython-314.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__/maintenance.cpython-314.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-310.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-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__/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-310.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-310.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-314.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-auto-update.cpython-314.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-followup-hygiene.cpython-314.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-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.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-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.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-reflection.cpython-314.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-email.cpython-314.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-synthesis.cpython-314.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/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/__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-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
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Deep Sleep v2 -- Phase 1: Collect all context for overnight analysis.
|
|
4
|
+
|
|
5
|
+
Gathers transcripts, DB data, logs, and discovered files into a single
|
|
6
|
+
plain-text context file that subsequent phases read via Claude's Read tool.
|
|
7
|
+
|
|
8
|
+
Environment variables:
|
|
9
|
+
NEXO_HOME -- root of the NEXO installation (default: ~/.nexo)
|
|
10
|
+
NEXO_CODE -- path to the NEXO source repo (optional, for self-analysis)
|
|
11
|
+
"""
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sqlite3
|
|
15
|
+
import sys
|
|
16
|
+
from datetime import datetime, timedelta
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
20
|
+
NEXO_CODE = Path(os.environ.get("NEXO_CODE", ""))
|
|
21
|
+
DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
|
|
22
|
+
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
23
|
+
COGNITIVE_DB = NEXO_HOME / "data" / "cognitive.db"
|
|
24
|
+
|
|
25
|
+
MIN_USER_MESSAGES = 3 # Skip trivial sessions
|
|
26
|
+
|
|
27
|
+
# ── Transcript collection (kept from collect_transcripts.py) ──────────────
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_session_dirs() -> list[Path]:
|
|
31
|
+
"""Find all Claude Code project directories that contain .jsonl files."""
|
|
32
|
+
claude_dir = Path.home() / ".claude" / "projects"
|
|
33
|
+
if not claude_dir.exists():
|
|
34
|
+
return []
|
|
35
|
+
dirs = set()
|
|
36
|
+
for jsonl in claude_dir.rglob("*.jsonl"):
|
|
37
|
+
dirs.add(jsonl.parent)
|
|
38
|
+
return list(dirs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extract_session(jsonl_path: Path) -> dict | None:
|
|
42
|
+
"""Extract clean transcript from a session JSONL file."""
|
|
43
|
+
messages = []
|
|
44
|
+
tool_uses = []
|
|
45
|
+
user_msg_count = 0
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
with open(jsonl_path, "r") as f:
|
|
49
|
+
for line_no, line in enumerate(f, 1):
|
|
50
|
+
line = line.strip()
|
|
51
|
+
if not line:
|
|
52
|
+
continue
|
|
53
|
+
try:
|
|
54
|
+
d = json.loads(line)
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
msg_type = d.get("type")
|
|
59
|
+
|
|
60
|
+
# User messages
|
|
61
|
+
if msg_type == "user":
|
|
62
|
+
content = d.get("message", {}).get("content", "")
|
|
63
|
+
if isinstance(content, str) and content.strip():
|
|
64
|
+
if content.startswith("<system-reminder>"):
|
|
65
|
+
continue
|
|
66
|
+
messages.append({
|
|
67
|
+
"role": "user",
|
|
68
|
+
"index": line_no,
|
|
69
|
+
"text": content[:5000],
|
|
70
|
+
"uuid": d.get("uuid", "")
|
|
71
|
+
})
|
|
72
|
+
user_msg_count += 1
|
|
73
|
+
|
|
74
|
+
# Assistant messages
|
|
75
|
+
elif msg_type in ("message", "assistant"):
|
|
76
|
+
msg = d.get("message", {})
|
|
77
|
+
content_blocks = msg.get("content", [])
|
|
78
|
+
text_parts = []
|
|
79
|
+
for block in content_blocks:
|
|
80
|
+
if isinstance(block, dict):
|
|
81
|
+
if block.get("type") == "text":
|
|
82
|
+
text_parts.append(block.get("text", ""))
|
|
83
|
+
elif block.get("type") == "tool_use":
|
|
84
|
+
tool_input = block.get("input", {})
|
|
85
|
+
tool_uses.append({
|
|
86
|
+
"tool": block.get("name", ""),
|
|
87
|
+
"input_keys": list(tool_input.keys()) if isinstance(tool_input, dict) else [],
|
|
88
|
+
"file": (
|
|
89
|
+
tool_input.get("file_path", "")
|
|
90
|
+
or str(tool_input.get("command", ""))[:100]
|
|
91
|
+
) if isinstance(tool_input, dict) else ""
|
|
92
|
+
})
|
|
93
|
+
if text_parts:
|
|
94
|
+
combined = "\n".join(text_parts)[:5000]
|
|
95
|
+
messages.append({
|
|
96
|
+
"role": "assistant",
|
|
97
|
+
"index": line_no,
|
|
98
|
+
"text": combined
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
print(f" [collect] Error reading {jsonl_path}: {e}", file=sys.stderr)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
if user_msg_count < MIN_USER_MESSAGES:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"session_file": jsonl_path.name,
|
|
110
|
+
"session_path": str(jsonl_path),
|
|
111
|
+
"message_count": len(messages),
|
|
112
|
+
"user_message_count": user_msg_count,
|
|
113
|
+
"tool_use_count": len(tool_uses),
|
|
114
|
+
"messages": messages,
|
|
115
|
+
"tool_uses": tool_uses
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def collect_transcripts(target_date: str) -> list[dict]:
|
|
120
|
+
"""Collect all sessions modified on the target date."""
|
|
121
|
+
sessions = []
|
|
122
|
+
for sdir in find_session_dirs():
|
|
123
|
+
for f in sdir.glob("*.jsonl"):
|
|
124
|
+
try:
|
|
125
|
+
mtime = datetime.fromtimestamp(f.stat().st_mtime)
|
|
126
|
+
except OSError:
|
|
127
|
+
continue
|
|
128
|
+
if mtime.strftime("%Y-%m-%d") == target_date:
|
|
129
|
+
session = extract_session(f)
|
|
130
|
+
if session:
|
|
131
|
+
session["modified"] = mtime.isoformat()
|
|
132
|
+
sessions.append(session)
|
|
133
|
+
sessions.sort(key=lambda s: s["modified"])
|
|
134
|
+
return sessions
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ── Database queries ──────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def safe_query(db_path: Path, query: str, params: tuple = ()) -> list[dict]:
|
|
141
|
+
"""Run a query and return rows as dicts. Returns [] on any error."""
|
|
142
|
+
if not db_path.exists():
|
|
143
|
+
return []
|
|
144
|
+
try:
|
|
145
|
+
conn = sqlite3.connect(str(db_path))
|
|
146
|
+
conn.row_factory = sqlite3.Row
|
|
147
|
+
rows = conn.execute(query, params).fetchall()
|
|
148
|
+
result = [dict(r) for r in rows]
|
|
149
|
+
conn.close()
|
|
150
|
+
return result
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f" [collect] DB query error ({db_path.name}): {e}", file=sys.stderr)
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def collect_followups() -> list[dict]:
|
|
157
|
+
"""Active followups from nexo.db."""
|
|
158
|
+
return safe_query(
|
|
159
|
+
NEXO_DB,
|
|
160
|
+
"SELECT * FROM followups WHERE status NOT IN ('COMPLETED', 'CANCELLED') ORDER BY date ASC"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def collect_learnings() -> list[dict]:
|
|
165
|
+
"""Active learnings from nexo.db."""
|
|
166
|
+
return safe_query(NEXO_DB, "SELECT * FROM learnings ORDER BY updated_at DESC LIMIT 200")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def collect_diaries(target_date: str) -> list[dict]:
|
|
170
|
+
"""Today's session diaries."""
|
|
171
|
+
# Diaries store created_at as unix timestamp or ISO string -- handle both
|
|
172
|
+
start_ts = datetime.strptime(target_date, "%Y-%m-%d").timestamp()
|
|
173
|
+
end_ts = start_ts + 86400
|
|
174
|
+
rows = safe_query(
|
|
175
|
+
NEXO_DB,
|
|
176
|
+
"SELECT * FROM session_diary WHERE created_at >= ? AND created_at < ? ORDER BY created_at ASC",
|
|
177
|
+
(start_ts, end_ts)
|
|
178
|
+
)
|
|
179
|
+
if not rows:
|
|
180
|
+
# Try ISO format
|
|
181
|
+
rows = safe_query(
|
|
182
|
+
NEXO_DB,
|
|
183
|
+
"SELECT * FROM session_diary WHERE created_at >= ? AND created_at < ? ORDER BY created_at ASC",
|
|
184
|
+
(target_date + "T00:00:00", target_date + "T23:59:59")
|
|
185
|
+
)
|
|
186
|
+
return rows
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def collect_trust_score() -> list[dict]:
|
|
190
|
+
"""Current trust score and 7-day history from cognitive.db."""
|
|
191
|
+
return safe_query(
|
|
192
|
+
COGNITIVE_DB,
|
|
193
|
+
"SELECT * FROM trust_score ORDER BY rowid DESC LIMIT 1"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ── Discovery: scan NEXO_HOME for non-core content ───────────────────────
|
|
198
|
+
|
|
199
|
+
CORE_DIRS = {"data", "operations", "logs", "coordination", "brain"}
|
|
200
|
+
CORE_FILES = {"config.json", "nexo.db", "cognitive.db"}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def discover_extras() -> list[dict]:
|
|
204
|
+
"""Scan NEXO_HOME for non-core directories and files."""
|
|
205
|
+
extras = []
|
|
206
|
+
if not NEXO_HOME.exists():
|
|
207
|
+
return extras
|
|
208
|
+
|
|
209
|
+
for item in sorted(NEXO_HOME.iterdir()):
|
|
210
|
+
name = item.name
|
|
211
|
+
if name.startswith("."):
|
|
212
|
+
continue
|
|
213
|
+
if name in CORE_DIRS or name in CORE_FILES:
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
entry = {"name": name, "path": str(item), "type": "dir" if item.is_dir() else "file"}
|
|
217
|
+
|
|
218
|
+
if item.is_dir():
|
|
219
|
+
# Count contents and list interesting files
|
|
220
|
+
files = list(item.rglob("*"))
|
|
221
|
+
entry["file_count"] = len([f for f in files if f.is_file()])
|
|
222
|
+
entry["notable_files"] = [
|
|
223
|
+
str(f.relative_to(item))
|
|
224
|
+
for f in files
|
|
225
|
+
if f.is_file() and f.suffix in (".py", ".sh", ".json", ".db", ".log", ".sqlite")
|
|
226
|
+
][:20]
|
|
227
|
+
elif item.is_file():
|
|
228
|
+
entry["size"] = item.stat().st_size
|
|
229
|
+
|
|
230
|
+
extras.append(entry)
|
|
231
|
+
|
|
232
|
+
return extras
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ── LaunchAgent logs ──────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def collect_error_logs(target_date: str) -> list[dict]:
|
|
239
|
+
"""Scan NEXO_HOME/logs/ for lines containing errors from today."""
|
|
240
|
+
log_dir = NEXO_HOME / "logs"
|
|
241
|
+
if not log_dir.exists():
|
|
242
|
+
return []
|
|
243
|
+
|
|
244
|
+
errors = []
|
|
245
|
+
for log_file in sorted(log_dir.glob("*.log")):
|
|
246
|
+
try:
|
|
247
|
+
lines = log_file.read_text(errors="replace").splitlines()
|
|
248
|
+
except Exception:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
file_errors = []
|
|
252
|
+
for i, line in enumerate(lines):
|
|
253
|
+
# Match lines from today that contain error indicators
|
|
254
|
+
if target_date in line and any(
|
|
255
|
+
kw in line.lower() for kw in ("error", "exception", "traceback", "failed", "fatal", "critical")
|
|
256
|
+
):
|
|
257
|
+
# Include surrounding context (1 line before, 2 after)
|
|
258
|
+
start = max(0, i - 1)
|
|
259
|
+
end = min(len(lines), i + 3)
|
|
260
|
+
file_errors.append({
|
|
261
|
+
"line": i + 1,
|
|
262
|
+
"context": "\n".join(lines[start:end])
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
if file_errors:
|
|
266
|
+
errors.append({
|
|
267
|
+
"file": log_file.name,
|
|
268
|
+
"path": str(log_file),
|
|
269
|
+
"errors": file_errors[:50] # Cap per file
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
return errors
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ── Format output as plain text ───────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def format_section(title: str, data, indent: int = 0) -> str:
|
|
279
|
+
"""Format a data section as readable plain text."""
|
|
280
|
+
prefix = " " * indent
|
|
281
|
+
lines = [f"\n{'=' * 70}", f"{title}", f"{'=' * 70}"]
|
|
282
|
+
|
|
283
|
+
if isinstance(data, list):
|
|
284
|
+
if not data:
|
|
285
|
+
lines.append(f"{prefix}(none)")
|
|
286
|
+
else:
|
|
287
|
+
for i, item in enumerate(data):
|
|
288
|
+
lines.append(f"\n{prefix}--- [{i + 1}] ---")
|
|
289
|
+
if isinstance(item, dict):
|
|
290
|
+
for k, v in item.items():
|
|
291
|
+
val_str = str(v)
|
|
292
|
+
if len(val_str) > 500:
|
|
293
|
+
val_str = val_str[:500] + "..."
|
|
294
|
+
lines.append(f"{prefix} {k}: {val_str}")
|
|
295
|
+
else:
|
|
296
|
+
lines.append(f"{prefix} {item}")
|
|
297
|
+
elif isinstance(data, dict):
|
|
298
|
+
for k, v in data.items():
|
|
299
|
+
val_str = str(v)
|
|
300
|
+
if len(val_str) > 500:
|
|
301
|
+
val_str = val_str[:500] + "..."
|
|
302
|
+
lines.append(f"{prefix}{k}: {val_str}")
|
|
303
|
+
elif isinstance(data, str):
|
|
304
|
+
lines.append(data)
|
|
305
|
+
else:
|
|
306
|
+
lines.append(str(data))
|
|
307
|
+
|
|
308
|
+
return "\n".join(lines)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def format_transcripts(sessions: list[dict]) -> str:
|
|
312
|
+
"""Format transcripts in a readable way for Claude to analyze."""
|
|
313
|
+
lines = [f"\n{'=' * 70}", "SESSION TRANSCRIPTS", f"{'=' * 70}"]
|
|
314
|
+
lines.append(f"Total sessions: {len(sessions)}")
|
|
315
|
+
|
|
316
|
+
for i, session in enumerate(sessions):
|
|
317
|
+
lines.append(f"\n{'─' * 60}")
|
|
318
|
+
lines.append(f"SESSION {i + 1}: {session['session_file']}")
|
|
319
|
+
lines.append(f"Modified: {session['modified']}")
|
|
320
|
+
lines.append(f"Messages: {session['message_count']}, Tool uses: {session['tool_use_count']}")
|
|
321
|
+
lines.append(f"{'─' * 60}")
|
|
322
|
+
|
|
323
|
+
for msg in session["messages"]:
|
|
324
|
+
role = "USER" if msg["role"] == "user" else "AGENT"
|
|
325
|
+
idx = msg.get("index", "?")
|
|
326
|
+
lines.append(f"\n[{role} @{idx}]")
|
|
327
|
+
lines.append(msg["text"])
|
|
328
|
+
|
|
329
|
+
if session["tool_uses"]:
|
|
330
|
+
lines.append(f"\n -- Tool usage log --")
|
|
331
|
+
for tu in session["tool_uses"]:
|
|
332
|
+
file_info = f" [{tu['file'][:80]}]" if tu.get("file") else ""
|
|
333
|
+
lines.append(f" - {tu['tool']}{file_info}")
|
|
334
|
+
|
|
335
|
+
return "\n".join(lines)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# ── Main ──────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def main():
|
|
342
|
+
target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
|
|
343
|
+
DEEP_SLEEP_DIR.mkdir(parents=True, exist_ok=True)
|
|
344
|
+
|
|
345
|
+
print(f"[collect] Phase 1: Collecting context for {target_date}")
|
|
346
|
+
|
|
347
|
+
# 1. Transcripts
|
|
348
|
+
print("[collect] Gathering transcripts...")
|
|
349
|
+
sessions = collect_transcripts(target_date)
|
|
350
|
+
print(f" Found {len(sessions)} sessions")
|
|
351
|
+
|
|
352
|
+
if not sessions:
|
|
353
|
+
print(f"[collect] No sessions found for {target_date}. Writing minimal context file.")
|
|
354
|
+
output_file = DEEP_SLEEP_DIR / f"{target_date}-context.txt"
|
|
355
|
+
output_file.write_text(
|
|
356
|
+
f"Deep Sleep Context for {target_date}\n\nNo sessions found for this date.\n"
|
|
357
|
+
)
|
|
358
|
+
print(f"[collect] Output: {output_file}")
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
# 2. Core DB data
|
|
362
|
+
print("[collect] Querying databases...")
|
|
363
|
+
followups = collect_followups()
|
|
364
|
+
print(f" Active followups: {len(followups)}")
|
|
365
|
+
|
|
366
|
+
learnings = collect_learnings()
|
|
367
|
+
print(f" Learnings: {len(learnings)}")
|
|
368
|
+
|
|
369
|
+
diaries = collect_diaries(target_date)
|
|
370
|
+
print(f" Diaries today: {len(diaries)}")
|
|
371
|
+
|
|
372
|
+
trust_history = collect_trust_score()
|
|
373
|
+
print(f" Trust events (7d): {len(trust_history)}")
|
|
374
|
+
|
|
375
|
+
# 3. Discovery
|
|
376
|
+
print("[collect] Scanning for non-core content...")
|
|
377
|
+
extras = discover_extras()
|
|
378
|
+
print(f" Discovered {len(extras)} extra items")
|
|
379
|
+
|
|
380
|
+
# 4. Error logs
|
|
381
|
+
print("[collect] Checking error logs...")
|
|
382
|
+
error_logs = collect_error_logs(target_date)
|
|
383
|
+
print(f" Log files with errors: {len(error_logs)}")
|
|
384
|
+
|
|
385
|
+
# 5. Build per-session files + shared context
|
|
386
|
+
date_dir = DEEP_SLEEP_DIR / target_date
|
|
387
|
+
date_dir.mkdir(parents=True, exist_ok=True)
|
|
388
|
+
print(f"[collect] Writing session files to {date_dir}/")
|
|
389
|
+
|
|
390
|
+
# Shared context (followups, learnings, diaries, etc.) — one file
|
|
391
|
+
shared_parts = [
|
|
392
|
+
f"Deep Sleep Shared Context -- {target_date}",
|
|
393
|
+
f"Generated at: {datetime.now().isoformat()}",
|
|
394
|
+
f"NEXO_HOME: {NEXO_HOME}",
|
|
395
|
+
f"Sessions: {len(sessions)}",
|
|
396
|
+
]
|
|
397
|
+
shared_parts.append(format_section("ACTIVE FOLLOWUPS", followups))
|
|
398
|
+
shared_parts.append(format_section("LEARNINGS (recent 200)", learnings))
|
|
399
|
+
shared_parts.append(format_section("SESSION DIARIES TODAY", diaries))
|
|
400
|
+
shared_parts.append(format_section("TRUST SCORE HISTORY (7d)", trust_history))
|
|
401
|
+
shared_parts.append(format_section("DISCOVERED NON-CORE CONTENT", extras))
|
|
402
|
+
shared_parts.append(format_section("ERROR LOGS", error_logs))
|
|
403
|
+
|
|
404
|
+
shared_text = "\n".join(shared_parts)
|
|
405
|
+
shared_file = date_dir / "shared-context.txt"
|
|
406
|
+
shared_file.write_text(shared_text, encoding="utf-8")
|
|
407
|
+
print(f" Shared context: {len(shared_text) / 1024:.0f} KB")
|
|
408
|
+
|
|
409
|
+
# Individual session files
|
|
410
|
+
session_files_written = []
|
|
411
|
+
total_size = len(shared_text.encode("utf-8"))
|
|
412
|
+
for i, session in enumerate(sessions):
|
|
413
|
+
sid_short = session["session_file"].replace(".jsonl", "")[:20]
|
|
414
|
+
filename = f"session-{i+1:02d}-{sid_short}.txt"
|
|
415
|
+
session_path = date_dir / filename
|
|
416
|
+
|
|
417
|
+
lines = [
|
|
418
|
+
f"Session: {session['session_file']}",
|
|
419
|
+
f"Modified: {session['modified']}",
|
|
420
|
+
f"Messages: {session['message_count']}, Tool uses: {session['tool_use_count']}",
|
|
421
|
+
f"{'─' * 60}",
|
|
422
|
+
]
|
|
423
|
+
for msg in session["messages"]:
|
|
424
|
+
role = "USER" if msg["role"] == "user" else "AGENT"
|
|
425
|
+
idx = msg.get("index", "?")
|
|
426
|
+
lines.append(f"\n[{role} @{idx}]")
|
|
427
|
+
lines.append(msg["text"])
|
|
428
|
+
|
|
429
|
+
if session["tool_uses"]:
|
|
430
|
+
lines.append(f"\n -- Tool usage log --")
|
|
431
|
+
for tu in session["tool_uses"]:
|
|
432
|
+
file_info = f" [{tu['file'][:80]}]" if tu.get("file") else ""
|
|
433
|
+
lines.append(f" - {tu['tool']}{file_info}")
|
|
434
|
+
|
|
435
|
+
session_text = "\n".join(lines)
|
|
436
|
+
session_path.write_text(session_text, encoding="utf-8")
|
|
437
|
+
session_files_written.append(filename)
|
|
438
|
+
total_size += len(session_text.encode("utf-8"))
|
|
439
|
+
print(f" {filename}: {len(session_text) / 1024:.0f} KB")
|
|
440
|
+
|
|
441
|
+
# Also keep legacy single context file for backwards compat
|
|
442
|
+
legacy_parts = [
|
|
443
|
+
f"Deep Sleep Context -- {target_date}",
|
|
444
|
+
f"Generated at: {datetime.now().isoformat()}",
|
|
445
|
+
f"NEXO_HOME: {NEXO_HOME}",
|
|
446
|
+
f"Sessions: {len(sessions)}",
|
|
447
|
+
]
|
|
448
|
+
legacy_parts.append(format_transcripts(sessions))
|
|
449
|
+
legacy_parts.append(shared_text)
|
|
450
|
+
legacy_file = DEEP_SLEEP_DIR / f"{target_date}-context.txt"
|
|
451
|
+
legacy_file.write_text("\n".join(legacy_parts), encoding="utf-8")
|
|
452
|
+
|
|
453
|
+
# Metadata JSON
|
|
454
|
+
meta = {
|
|
455
|
+
"date": target_date,
|
|
456
|
+
"sessions_found": len(sessions),
|
|
457
|
+
"session_files": [s["session_file"] for s in sessions],
|
|
458
|
+
"session_txt_files": session_files_written,
|
|
459
|
+
"total_messages": sum(s["message_count"] for s in sessions),
|
|
460
|
+
"total_tool_uses": sum(s["tool_use_count"] for s in sessions),
|
|
461
|
+
"followups_active": len(followups),
|
|
462
|
+
"learnings_count": len(learnings),
|
|
463
|
+
"diaries_today": len(diaries),
|
|
464
|
+
"error_log_files": len(error_logs),
|
|
465
|
+
"date_dir": str(date_dir),
|
|
466
|
+
"shared_context_file": str(shared_file),
|
|
467
|
+
"context_file": str(legacy_file),
|
|
468
|
+
"total_size_bytes": total_size,
|
|
469
|
+
}
|
|
470
|
+
meta_file = DEEP_SLEEP_DIR / f"{target_date}-meta.json"
|
|
471
|
+
with open(meta_file, "w") as f:
|
|
472
|
+
json.dump(meta, f, indent=2, ensure_ascii=False)
|
|
473
|
+
|
|
474
|
+
print(f"\n[collect] Done. {len(session_files_written)} session files + shared context ({total_size / 1024:.0f} KB total)")
|
|
475
|
+
print(f"[collect] Dir: {date_dir}")
|
|
476
|
+
print(f"[collect] Meta: {meta_file}")
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == "__main__":
|
|
480
|
+
main()
|