nexo-brain 2.3.0 → 2.3.1

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.
Files changed (287) hide show
  1. package/README.md +1 -1
  2. package/package.json +6 -3
  3. package/src/auto_update.py +1 -0
  4. package/src/crons/sync.py +1 -2
  5. package/src/db/_core.py +1 -0
  6. package/src/db/_entities.py +1 -0
  7. package/src/db/_episodic.py +1 -0
  8. package/src/db/_learnings.py +1 -0
  9. package/src/db/_reminders.py +1 -0
  10. package/src/db/_sessions.py +1 -0
  11. package/src/db/_skills.py +1 -0
  12. package/src/plugin_loader.py +1 -0
  13. package/src/plugins/update.py +1 -0
  14. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  15. package/src/scripts/deep-sleep/collect.py +1 -0
  16. package/src/scripts/deep-sleep/extract.py +1 -0
  17. package/src/scripts/deep-sleep/synthesize.py +1 -0
  18. package/src/scripts/nexo-learning-housekeep.py +1 -0
  19. package/src/scripts/nexo-watchdog.sh +19 -11
  20. package/src/server.py +1 -0
  21. package/src/tools_coordination.py +1 -0
  22. package/src/tools_sessions.py +1 -0
  23. package/scripts/migrate-to-unified 2.sh +0 -813
  24. package/scripts/migrate-to-unified.sh +0 -813
  25. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  26. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  27. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  28. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  29. package/scripts/nexo-preflight.sh +0 -236
  30. package/scripts/pre-commit-check 2.sh +0 -55
  31. package/scripts/pre-commit-check.sh +0 -55
  32. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  33. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  34. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  35. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  36. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  37. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  38. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  39. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  40. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  41. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  42. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  43. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  44. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  45. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  46. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  47. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  48. package/src/auto_close_sessions 2.py +0 -159
  49. package/src/auto_update 2.py +0 -634
  50. package/src/claim_graph 2.py +0 -323
  51. package/src/cognitive/__init__ 2.py +0 -62
  52. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  53. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  54. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  55. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  56. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  57. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  58. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  59. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  60. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  61. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  62. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  63. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  64. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  73. package/src/cognitive/_core 2.py +0 -567
  74. package/src/cognitive/_decay 2.py +0 -382
  75. package/src/cognitive/_ingest 2.py +0 -892
  76. package/src/cognitive/_memory 2.py +0 -912
  77. package/src/cognitive/_search 2.py +0 -949
  78. package/src/cognitive/_trust 2.py +0 -464
  79. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  80. package/src/crons/manifest 2.json +0 -106
  81. package/src/crons/sync 2.py +0 -217
  82. package/src/dashboard/__init__ 2.py +0 -0
  83. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  84. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  85. package/src/dashboard/app 2.py +0 -789
  86. package/src/db/__init__ 2.py +0 -89
  87. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  90. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  91. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  92. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  93. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  94. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  95. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  96. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  97. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  98. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  99. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  100. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  101. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  102. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  103. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  104. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  105. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  106. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  107. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  108. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  109. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  128. package/src/db/_core 2.py +0 -417
  129. package/src/db/_credentials 2.py +0 -124
  130. package/src/db/_entities 2.py +0 -178
  131. package/src/db/_episodic 2.py +0 -738
  132. package/src/db/_evolution 2.py +0 -54
  133. package/src/db/_fts 2.py +0 -406
  134. package/src/db/_learnings 2.py +0 -168
  135. package/src/db/_reminders 2.py +0 -338
  136. package/src/db/_schema 2.py +0 -364
  137. package/src/db/_sessions 2.py +0 -300
  138. package/src/db/_tasks 2.py +0 -91
  139. package/src/evolution_cycle 2.py +0 -266
  140. package/src/hnsw_index 2.py +0 -254
  141. package/src/hooks/auto_capture 2.py +0 -208
  142. package/src/hooks/caffeinate-guard 2.sh +0 -8
  143. package/src/hooks/capture-session 2.sh +0 -21
  144. package/src/hooks/capture-tool-logs 2.sh +0 -127
  145. package/src/hooks/daily-briefing-check 2.sh +0 -33
  146. package/src/hooks/inbox-hook 2.sh +0 -76
  147. package/src/hooks/post-compact 2.sh +0 -148
  148. package/src/hooks/pre-compact 2.sh +0 -151
  149. package/src/hooks/session-start 2.sh +0 -268
  150. package/src/hooks/session-stop 2.sh +0 -140
  151. package/src/kg_populate 2.py +0 -290
  152. package/src/knowledge_graph 2.py +0 -257
  153. package/src/maintenance 2.py +0 -59
  154. package/src/migrate_embeddings 2.py +0 -122
  155. package/src/plugin_loader 2.py +0 -202
  156. package/src/plugins/__init__ 2.py +0 -0
  157. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  158. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  159. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  160. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  161. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  162. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  163. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  164. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  165. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  166. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  167. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  168. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  169. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  172. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  175. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  189. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  191. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  193. package/src/plugins/adaptive_mode 2.py +0 -805
  194. package/src/plugins/agents 2.py +0 -52
  195. package/src/plugins/artifact_registry 2.py +0 -450
  196. package/src/plugins/backup 2.py +0 -104
  197. package/src/plugins/cognitive_memory 2.py +0 -564
  198. package/src/plugins/core_rules 2.py +0 -252
  199. package/src/plugins/cortex 2.py +0 -299
  200. package/src/plugins/entities 2.py +0 -67
  201. package/src/plugins/episodic_memory 2.py +0 -533
  202. package/src/plugins/evolution 2.py +0 -115
  203. package/src/plugins/guard 2.py +0 -746
  204. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  205. package/src/plugins/preferences 2.py +0 -47
  206. package/src/plugins/update 2.py +0 -256
  207. package/src/requirements 2.txt +0 -12
  208. package/src/rules/__init__ 2.py +0 -0
  209. package/src/rules/core-rules 2.json +0 -331
  210. package/src/rules/migrate 2.py +0 -207
  211. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  212. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  213. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  214. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  215. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  216. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  217. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  218. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  219. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  220. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  221. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  222. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  223. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  232. package/src/scripts/check-context 2.py +0 -264
  233. package/src/scripts/nexo-auto-update 2.py +0 -6
  234. package/src/scripts/nexo-backup 2.sh +0 -25
  235. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  236. package/src/scripts/nexo-catchup 2.py +0 -242
  237. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  238. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  239. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  240. package/src/scripts/nexo-evolution-run 2.py +0 -597
  241. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  242. package/src/scripts/nexo-github-monitor 2.py +0 -256
  243. package/src/scripts/nexo-immune 2.py +0 -927
  244. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  245. package/src/scripts/nexo-install 2.py +0 -6
  246. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  247. package/src/scripts/nexo-learning-validator 2.py +0 -207
  248. package/src/scripts/nexo-migrate 2.py +0 -232
  249. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  250. package/src/scripts/nexo-pre-commit 2.py +0 -120
  251. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  252. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  253. package/src/scripts/nexo-reflection 2.py +0 -253
  254. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  255. package/src/scripts/nexo-send-email 2.py +0 -25
  256. package/src/scripts/nexo-send-email.py +0 -25
  257. package/src/scripts/nexo-send-reply 2.py +0 -178
  258. package/src/scripts/nexo-send-reply.py +0 -178
  259. package/src/scripts/nexo-sleep 2.py +0 -592
  260. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  261. package/src/scripts/nexo-synthesis 2.py +0 -253
  262. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  263. package/src/scripts/nexo-update 2.sh +0 -161
  264. package/src/scripts/nexo-watchdog 2.sh +0 -878
  265. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  266. package/src/server 2.py +0 -733
  267. package/src/storage_router 2.py +0 -32
  268. package/src/tools_coordination 2.py +0 -102
  269. package/src/tools_credentials 2.py +0 -68
  270. package/src/tools_learnings 2.py +0 -220
  271. package/src/tools_menu 2.py +0 -227
  272. package/src/tools_reminders 2.py +0 -86
  273. package/src/tools_reminders_crud 2.py +0 -159
  274. package/src/tools_sessions 2.py +0 -476
  275. package/src/tools_task_history 2.py +0 -57
  276. package/templates/CLAUDE.md 2.template +0 -63
  277. package/templates/openclaw 2.json +0 -13
  278. package/tests/__init__ 2.py +0 -0
  279. package/tests/__init__.py +0 -0
  280. package/tests/conftest 2.py +0 -71
  281. package/tests/conftest.py +0 -71
  282. package/tests/test_cognitive 2.py +0 -205
  283. package/tests/test_cognitive.py +0 -205
  284. package/tests/test_knowledge_graph 2.py +0 -140
  285. package/tests/test_knowledge_graph.py +0 -140
  286. package/tests/test_migrations 2.py +0 -137
  287. package/tests/test_migrations.py +0 -137
@@ -1,151 +0,0 @@
1
- #!/bin/bash
2
- # NEXO PreCompact Hook — Save checkpoint + inject preservation instructions
3
- # This runs BEFORE Claude Code compacts. It:
4
- # 1. Enriches the session checkpoint in SQLite with latest diary draft data
5
- # 2. Injects a systemMessage telling the operator to save any WIP via MCP tools
6
- set -uo pipefail
7
-
8
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
9
- NEXO_DB="$NEXO_HOME/data/nexo.db"
10
- mkdir -p "$NEXO_HOME/data"
11
- TODAY=$(date +%Y-%m-%d)
12
- LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
13
- LOG_LINES=0
14
- if [ -f "$LOG_FILE" ]; then
15
- LOG_LINES=$(wc -l < "$LOG_FILE" | tr -d ' ')
16
- fi
17
-
18
- # Enrich checkpoint: copy diary draft context into checkpoint if exists
19
- if [ -f "$NEXO_DB" ]; then
20
- # Get latest active session's diary draft
21
- LATEST_SID=$(sqlite3 "$NEXO_DB" "
22
- SELECT sid FROM sessions ORDER BY last_update_epoch DESC LIMIT 1
23
- " 2>/dev/null || echo "")
24
-
25
- if [ -n "$LATEST_SID" ]; then
26
- # Write SID to temp file so PostCompact knows which session compacted
27
- echo "$LATEST_SID" > /tmp/nexo-compacting-sid
28
- # Pull diary draft data into checkpoint
29
- sqlite3 "$NEXO_DB" "
30
- INSERT INTO session_checkpoints (sid, task, current_goal, updated_at)
31
- SELECT s.sid, s.task, COALESCE(d.last_context_hint, s.task), datetime('now')
32
- FROM sessions s
33
- LEFT JOIN session_diary_draft d ON d.sid = s.sid
34
- WHERE s.sid = '$LATEST_SID'
35
- ON CONFLICT(sid) DO UPDATE SET
36
- task = excluded.task,
37
- current_goal = CASE
38
- WHEN excluded.current_goal != '' THEN excluded.current_goal
39
- ELSE session_checkpoints.current_goal
40
- END,
41
- updated_at = datetime('now')
42
- " 2>/dev/null || true
43
- fi
44
- fi
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
-
147
- cat << HOOKEOF
148
- {
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."
150
- }
151
- HOOKEOF
@@ -1,268 +0,0 @@
1
- #!/bin/bash
2
- # NEXO SessionStart hook — generates a comprehensive briefing
3
- # Reads SQLite directly for reminders, followups, active sessions.
4
- # Caches output for 1 hour to avoid regenerating on rapid successive sessions.
5
- set -uo pipefail
6
-
7
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
8
- BRIEFING_FILE="$NEXO_HOME/coordination/session-briefing.txt"
9
- MAX_AGE_SECONDS=3600 # 1 hour cache
10
-
11
- mkdir -p "$NEXO_HOME/coordination" "$NEXO_HOME/operations"
12
-
13
- # Write session start timestamp for session-scoped tool counting
14
- date +%s > "$NEXO_HOME/operations/.session-start-ts"
15
-
16
- # Clean up post-mortem flag from previous session
17
- rm -f "$NEXO_HOME/operations/.postmortem-complete" 2>/dev/null
18
-
19
- # Capture Claude Code session_id for inter-terminal inbox hook
20
- HOOK_INPUT=$(cat || true)
21
- CLAUDE_SID=""
22
- if [ -n "$HOOK_INPUT" ]; then
23
- CLAUDE_SID=$(echo "$HOOK_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" 2>/dev/null || true)
24
- fi
25
- if [ -n "$CLAUDE_SID" ]; then
26
- echo "$CLAUDE_SID" > "/tmp/nexo-claude-sid-${CLAUDE_SID}"
27
- # Also write to a predictable location for the startup prompt
28
- echo "$CLAUDE_SID" > "$NEXO_HOME/coordination/.claude-session-id"
29
- fi
30
-
31
- # If briefing exists and is less than 1 hour old, skip regeneration
32
- if [ -f "$BRIEFING_FILE" ]; then
33
- if [ "$(uname)" = "Darwin" ]; then
34
- file_age=$(( $(date +%s) - $(stat -f %m "$BRIEFING_FILE") ))
35
- else
36
- file_age=$(( $(date +%s) - $(stat -c %Y "$BRIEFING_FILE") ))
37
- fi
38
- if [ "$file_age" -lt "$MAX_AGE_SECONDS" ]; then
39
- exit 0
40
- fi
41
- fi
42
-
43
- TODAY=$(date +%Y-%m-%d)
44
- WEEKDAY=$(date +%A)
45
-
46
- # Generate briefing from SQLite
47
- python3 -c "
48
- import json, os, sys
49
- from datetime import date
50
-
51
- today_str = '$TODAY'
52
- weekday = '$WEEKDAY'
53
- nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
54
- db_path = os.path.join(nexo_home, 'data', 'nexo.db')
55
-
56
- lines = []
57
- lines.append(f'## Date: {today_str} ({weekday})')
58
- lines.append('')
59
-
60
- # Read from SQLite
61
- reminders_rows = []
62
- followups_rows = []
63
- sessions = []
64
- sqlite_ok = True
65
-
66
- try:
67
- import sqlite3
68
- if not os.path.exists(db_path):
69
- sqlite_ok = False
70
- else:
71
- db = sqlite3.connect(db_path, timeout=10)
72
- db.execute('PRAGMA journal_mode=WAL')
73
- db.execute('PRAGMA busy_timeout=10000')
74
- db.row_factory = sqlite3.Row
75
-
76
- try:
77
- reminders_rows = [dict(r) for r in db.execute(
78
- 'SELECT id, date, description, status, category FROM reminders '
79
- 'WHERE status NOT LIKE \"%COMPLET%\" AND status NOT LIKE \"%DELET%\" '
80
- 'AND status NOT LIKE \"%COMPLETED%\" AND status NOT LIKE \"%DELETED%\"'
81
- ).fetchall()]
82
- except Exception:
83
- pass
84
-
85
- try:
86
- followups_rows = [dict(r) for r in db.execute(
87
- 'SELECT id, date, description, status FROM followups '
88
- 'WHERE status NOT LIKE \"%COMPLET%\" AND status NOT LIKE \"%COMPLETED%\"'
89
- ).fetchall()]
90
- except Exception:
91
- pass
92
-
93
- try:
94
- rows = db.execute(
95
- 'SELECT sid, task, started FROM sessions '
96
- 'WHERE completed=0 AND (strftime(\"%s\",\"now\") - last_update) < 900'
97
- ).fetchall()
98
- sessions = [{'sid': r['sid'], 'task': r['task'], 'started': r['started'][:16]} for r in rows]
99
- except Exception:
100
- pass
101
-
102
- db.close()
103
- except Exception:
104
- sqlite_ok = False
105
-
106
- if not sqlite_ok:
107
- lines.append('Database not initialized yet. Run nexo_startup to begin.')
108
- lines.append('')
109
- print('\n'.join(lines))
110
- sys.exit(0)
111
-
112
- # Overdue reminders
113
- lines.append('## Overdue Reminders')
114
- found = False
115
- for r in reminders_rows:
116
- rdate = r.get('date', '')
117
- if rdate and rdate[:10] < today_str:
118
- try:
119
- delta = (date.fromisoformat(today_str) - date.fromisoformat(rdate[:10])).days
120
- except:
121
- delta = '?'
122
- desc = (r.get('description', '') or '')[:120]
123
- lines.append(f'- [{r[\"id\"]}] {rdate} {desc} -- {delta} day(s) overdue')
124
- found = True
125
- if not found:
126
- lines.append('NONE')
127
- lines.append('')
128
-
129
- # Today's reminders
130
- lines.append('## Reminders Due Today')
131
- found = False
132
- for r in reminders_rows:
133
- rdate = r.get('date', '')
134
- if rdate and rdate[:10] == today_str:
135
- desc = (r.get('description', '') or '')[:120]
136
- lines.append(f'- [{r[\"id\"]}] {desc}')
137
- found = True
138
- if not found:
139
- lines.append('NONE')
140
- lines.append('')
141
-
142
- # Pending followups (due today or overdue)
143
- lines.append('## Followups Due Today or Overdue')
144
- found = False
145
- for r in followups_rows:
146
- fdate = r.get('date', '')
147
- if fdate and fdate[:10] <= today_str:
148
- desc = (r.get('description', '') or '')[:100]
149
- lines.append(f'- [{r[\"id\"]}] {fdate} {desc}')
150
- found = True
151
- if not found:
152
- lines.append('NONE')
153
- lines.append('')
154
-
155
- # Active sessions
156
- lines.append('## Active Sessions')
157
- if sessions:
158
- for s in sessions:
159
- lines.append(f'- [{s[\"sid\"]}] {s[\"task\"]} (since {s[\"started\"]})')
160
- else:
161
- lines.append('NONE')
162
- lines.append('')
163
-
164
- # Last self-audit
165
- audit_file = os.path.join(nexo_home, 'logs', 'self-audit-summary.json')
166
- if os.path.exists(audit_file):
167
- try:
168
- audit = json.load(open(audit_file))
169
- lines.append('## Last Self-Audit')
170
- lines.append(json.dumps(audit, indent=2)[:500])
171
- lines.append('')
172
- except Exception:
173
- pass
174
-
175
- print('\n'.join(lines))
176
- " > "$BRIEFING_FILE" 2>/dev/null
177
-
178
- # If generation failed, write minimal briefing
179
- if [ ! -s "$BRIEFING_FILE" ]; then
180
- echo "## Briefing unavailable — generation error. Use nexo_reminders MCP for fresh data." > "$BRIEFING_FILE"
181
- fi
182
-
183
- # ─── Semantic Context: recent work sessions ───
184
- # Append recent session summaries for immediate context
185
- CLAUDE_MEM_DB="$NEXO_HOME/claude-mem.db"
186
-
187
- if [ -f "$CLAUDE_MEM_DB" ]; then
188
- RECENT_SESSIONS=$(python3 -c "
189
- import sqlite3, sys
190
- try:
191
- db = sqlite3.connect('$CLAUDE_MEM_DB')
192
- rows = db.execute('''
193
- SELECT created_at, request, learned, completed
194
- FROM session_summaries
195
- ORDER BY id DESC LIMIT 5
196
- ''').fetchall()
197
- db.close()
198
- if rows:
199
- print()
200
- print('## Last 5 Work Sessions')
201
- for r in rows:
202
- date = r[0][:16] if r[0] else '?'
203
- req = (r[1] or '')[:120]
204
- learned = (r[2] or '')[:100]
205
- print(f'- [{date}] {req}')
206
- if learned:
207
- print(f' -> {learned}')
208
- except Exception as e:
209
- pass
210
- " 2>/dev/null)
211
-
212
- if [ -n "$RECENT_SESSIONS" ]; then
213
- echo "$RECENT_SESSIONS" >> "$BRIEFING_FILE"
214
- fi
215
- fi
216
-
217
- # ─── Cortex Report: what happened while user was away ───
218
- # Check brain/ (canonical) first, fall back to cortex/ (legacy)
219
- CORTEX_BRIEFING="$NEXO_HOME/brain/last-briefing.json"
220
- if [ ! -f "$CORTEX_BRIEFING" ] && [ -f "$NEXO_HOME/cortex/last-briefing.json" ]; then
221
- CORTEX_BRIEFING="$NEXO_HOME/cortex/last-briefing.json"
222
- fi
223
- if [ -f "$CORTEX_BRIEFING" ]; then
224
- CORTEX_SECTION=$(python3 -c "
225
- import json
226
- try:
227
- data = json.load(open('$CORTEX_BRIEFING'))
228
- ts = data.get('timestamp', '?')
229
- actions = data.get('actions_taken', [])
230
- signals = data.get('signals_active', [])
231
- recommendations = data.get('recommendations', [])
232
- pending_q = data.get('pending_questions_unanswered', [])
233
- dmn_summary = data.get('dmn_summary', '')
234
-
235
- print()
236
- print('## Cortex Report (last update: ' + str(ts)[:16] + ')')
237
- if actions:
238
- print('### Actions Executed')
239
- for a in actions[-10:]:
240
- if isinstance(a, dict):
241
- print(f'- [{a.get(\"type\",\"?\")}] {a.get(\"detail\",\"\")}')
242
- else:
243
- print(f'- {a}')
244
- if signals:
245
- print('### Active Signals')
246
- for s in signals[:5]:
247
- print(f'- {s}')
248
- if recommendations:
249
- print('### Recommendations')
250
- for r in recommendations[:3]:
251
- print(f'- {r}')
252
- if pending_q:
253
- print(f'### Unanswered Questions: {len(pending_q)}')
254
- for q in pending_q[:3]:
255
- if isinstance(q, dict):
256
- print(f'- {q.get(\"question\",\"?\")}')
257
- else:
258
- print(f'- {q}')
259
- if dmn_summary:
260
- print(f'### Last DMN: {str(dmn_summary)[:200]}')
261
- except Exception as e:
262
- print(f'## Cortex Report: error reading briefing ({e})')
263
- " 2>/dev/null)
264
-
265
- if [ -n "$CORTEX_SECTION" ]; then
266
- echo "$CORTEX_SECTION" >> "$BRIEFING_FILE"
267
- fi
268
- fi
@@ -1,140 +0,0 @@
1
- #!/bin/bash
2
- # NEXO Memory Stop Hook (v7 — BLOCKING post-mortem with trivial session detection)
3
- #
4
- # v5 bug: used "approve" + systemMessage — AI never processed post-mortem.
5
- # v6 fix: uses "block" — but blocked ALL sessions including trivial ones.
6
- # v7 fix: detects trivial sessions (<5 tool calls) and approves immediately.
7
- # Non-trivial sessions get blocked until post-mortem is done.
8
- #
9
- # Flow:
10
- # Trivial session (quick question, <5 tool calls):
11
- # → APPROVE immediately, no post-mortem needed
12
- #
13
- # Non-trivial session:
14
- # 1. User closes → hook checks flag → not found → BLOCK
15
- # 2. AI executes post-mortem → creates flag
16
- # 3. User closes again → hook sees flag → APPROVE
17
- set -uo pipefail
18
-
19
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
20
- FLAG_FILE="$NEXO_HOME/operations/.postmortem-complete"
21
- TODAY=$(date +%Y-%m-%d)
22
- TOOL_LOG="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
23
-
24
- # 0. Refresh diary draft with latest changes/decisions (best-effort)
25
- python3 -c "
26
- import sys, json, os
27
- nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
28
- nexo_code = os.environ.get('NEXO_CODE', nexo_home)
29
- sys.path.insert(0, nexo_code)
30
- os.environ['NEXO_SKIP_FS_INDEX'] = '1'
31
- from db import init_db, get_db, get_active_sessions, upsert_diary_draft, get_diary_draft
32
- init_db()
33
- conn = get_db()
34
- sessions = get_active_sessions()
35
- for s in sessions:
36
- sid = s['sid']
37
- draft = get_diary_draft(sid)
38
- if not draft:
39
- continue
40
- change_ids = [r[0] for r in conn.execute('SELECT id FROM change_log WHERE session_id = ?', (sid,)).fetchall()]
41
- decision_ids = [r[0] for r in conn.execute('SELECT id FROM decisions WHERE session_id = ?', (sid,)).fetchall()]
42
- upsert_diary_draft(
43
- sid=sid,
44
- tasks_seen=draft['tasks_seen'],
45
- change_ids=json.dumps(change_ids),
46
- decision_ids=json.dumps(decision_ids),
47
- last_context_hint=draft['last_context_hint'],
48
- heartbeat_count=draft['heartbeat_count'],
49
- summary_draft=draft['summary_draft'],
50
- )
51
- " 2>/dev/null || true
52
-
53
- # 1. Detect trivial session — count meaningful tool calls from THIS session only
54
- # Uses .session-start-ts written by SessionStart hook
55
- # A session with <5 tool calls (excluding Read/Grep/Glob/Bash) is trivial
56
- SESSION_START_TS="$NEXO_HOME/operations/.session-start-ts"
57
-
58
- # 0.5. Detect non-interactive (claude -p) sessions — skip post-mortem entirely
59
- # SessionStart hook writes .session-start-ts. If missing or stale (>30 min),
60
- # this is likely a -p script session — approve immediately.
61
- # Also skip if NEXO_HEADLESS=1 is set (explicit headless mode for scripts).
62
- if [ "${NEXO_HEADLESS:-}" = "1" ] || [ ! -f "$SESSION_START_TS" ] || [ "$(($(date +%s) - $(cat "$SESSION_START_TS" 2>/dev/null || echo 0)))" -gt 1800 ]; then
63
- cat << 'HOOKEOF'
64
- {
65
- "decision": "approve"
66
- }
67
- HOOKEOF
68
- exit 0
69
- fi
70
- SESSION_START=0
71
- if [ -f "$SESSION_START_TS" ]; then
72
- SESSION_START=$(cat "$SESSION_START_TS" 2>/dev/null || echo "0")
73
- fi
74
-
75
- TOOL_COUNT=0
76
- if [ -f "$TOOL_LOG" ]; then
77
- TOOL_COUNT=$(python3 -c "
78
- import json, sys, os
79
- session_start = float(os.environ.get('SESSION_START', '0'))
80
- count = 0
81
- for line in open('$TOOL_LOG'):
82
- try:
83
- d = json.loads(line)
84
- # Only count tools from THIS session (after session-start-ts)
85
- ts = d.get('timestamp', '')
86
- if ts and session_start > 0:
87
- from datetime import datetime
88
- try:
89
- entry_ts = datetime.fromisoformat(ts.replace('Z', '+00:00')).timestamp()
90
- if entry_ts < session_start:
91
- continue
92
- except:
93
- pass
94
- t = d.get('tool_name', '')
95
- if t and t not in ('Read', 'Grep', 'Glob', 'Bash', 'ToolSearch'):
96
- count += 1
97
- except:
98
- pass
99
- print(count)
100
- " 2>/dev/null || echo "0")
101
- fi
102
-
103
- # Trivial session → approve immediately, no buffer writing, skip post-mortem
104
- if [ "$TOOL_COUNT" -lt 5 ]; then
105
- cat << 'HOOKEOF'
106
- {
107
- "decision": "approve"
108
- }
109
- HOOKEOF
110
- exit 0
111
- fi
112
-
113
- # 2. Non-trivial session — check if post-mortem was already completed
114
- # Flag has NO TTL — it persists until SessionStart cleans it up next session.
115
- POSTMORTEM_DONE=false
116
- if [ -f "$FLAG_FILE" ]; then
117
- POSTMORTEM_DONE=true
118
- fi
119
-
120
- if [ "$POSTMORTEM_DONE" = true ]; then
121
- # Post-mortem was done — allow session to close
122
- # NOTE: do NOT delete flag here — SessionStart cleans it up next session
123
- cat << 'HOOKEOF'
124
- {
125
- "decision": "approve"
126
- }
127
- HOOKEOF
128
- else
129
- # Post-mortem NOT done — BLOCK session close and inject instructions
130
- cat << 'HOOKEOF'
131
- {
132
- "decision": "block",
133
- "reason": "STOP HOOK — MANDATORY POST-MORTEM before ending (do NOT ask permission, do NOT skip):\n\n## 1. SELF-CRITIQUE (MANDATORY — write to session diary)\nAnswer these questions in the self_critique field of nexo_session_diary_write:\n- Did the user have to ask me for something I should have detected or done on my own?\n- Did I wait for the user to tell me something I could have verified proactively?\n- Are there systems/states I can check next session without being asked?\n- Did I repeat an error that already had a registered learning?\n- What would I do differently if I repeated this session?\nIf any answer is YES — write the specific rule that would prevent repetition.\nIf the session was flawless, write 'No self-critique — clean session.'\n\n## 2. SESSION BUFFER\nIf the session was NOT trivial, append ONE JSON line to $NEXO_HOME/brain/session_buffer.jsonl:\n{\"ts\":\"YYYY-MM-DDTHH:MM:SS\",\"tasks\":[...],\"decisions\":[...],\"user_patterns\":[...],\"files_modified\":[...],\"errors_resolved\":[...],\"self_critique\":\"short summary\",\"mood\":\"focused|impatient|exploratory|frustrated|satisfied|neutral\",\"source\":\"claude\"}\n\n## 3. FOLLOWUPS\nIf there were deploys/cron changes/fixes — nexo_followup_create with verification date.\n\n## 4. PROACTIVE SEEDS\nWhat can I leave prepared so the next session starts doing useful work without the user asking?\n\n## 5. MARK COMPLETE\nWhen ALL of the above is done, run:\nbash -c 'mkdir -p $NEXO_HOME/operations && date +%s > $NEXO_HOME/operations/.postmortem-complete'\nThe user will close again and the hook will approve.\n\nIMPORTANT: Do NOT say goodbye, do NOT say goodnight or any farewell. Just execute the steps and mark complete."
134
- }
135
- HOOKEOF
136
- fi
137
-
138
- # 3. Session buffer fallback REMOVED (v8)
139
- # The old hook-fallback was 86% noise. Session diary (written by Claude during
140
- # post-mortem) is the only source of truth now. No more buffer writing.