nexo-brain 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +1 -1
  2. package/bin/nexo-brain.js +92 -9
  3. package/bin/postinstall.js +22 -15
  4. package/package.json +7 -4
  5. package/src/auto_update.py +194 -5
  6. package/src/crons/sync.py +6 -2
  7. package/src/db/_core.py +1 -0
  8. package/src/db/_entities.py +1 -0
  9. package/src/db/_episodic.py +1 -0
  10. package/src/db/_learnings.py +1 -0
  11. package/src/db/_reminders.py +1 -0
  12. package/src/db/_schema.py +11 -1
  13. package/src/db/_sessions.py +1 -0
  14. package/src/db/_skills.py +1 -0
  15. package/src/hooks/capture-tool-logs.sh +23 -6
  16. package/src/hooks/session-start.sh +4 -3
  17. package/src/plugin_loader.py +1 -0
  18. package/src/plugins/update.py +377 -26
  19. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  20. package/src/scripts/deep-sleep/collect.py +1 -0
  21. package/src/scripts/deep-sleep/extract.py +1 -0
  22. package/src/scripts/deep-sleep/synthesize.py +1 -0
  23. package/src/scripts/nexo-catchup.py +29 -4
  24. package/src/scripts/nexo-daily-self-audit.py +21 -1
  25. package/src/scripts/nexo-evolution-run.py +21 -1
  26. package/src/scripts/nexo-learning-housekeep.py +1 -0
  27. package/src/scripts/nexo-postmortem-consolidator.py +34 -9
  28. package/src/scripts/nexo-sleep.py +32 -10
  29. package/src/scripts/nexo-synthesis.py +29 -9
  30. package/src/scripts/nexo-update.sh +109 -7
  31. package/src/scripts/nexo-watchdog.sh +122 -58
  32. package/src/server.py +66 -1
  33. package/src/tools_coordination.py +1 -0
  34. package/src/tools_sessions.py +1 -0
  35. package/scripts/migrate-to-unified 2.sh +0 -813
  36. package/scripts/migrate-to-unified.sh +0 -813
  37. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  38. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  39. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  40. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  41. package/scripts/nexo-preflight.sh +0 -236
  42. package/scripts/pre-commit-check 2.sh +0 -55
  43. package/scripts/pre-commit-check.sh +0 -55
  44. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  45. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  46. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  47. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  48. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  49. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  50. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  51. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  52. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  53. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  54. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  55. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  56. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  57. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  58. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  59. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  60. package/src/auto_close_sessions 2.py +0 -159
  61. package/src/auto_update 2.py +0 -634
  62. package/src/claim_graph 2.py +0 -323
  63. package/src/cognitive/__init__ 2.py +0 -62
  64. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  73. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  74. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  75. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  76. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  77. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  78. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  79. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  80. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  81. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  82. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  83. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  84. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  85. package/src/cognitive/_core 2.py +0 -567
  86. package/src/cognitive/_decay 2.py +0 -382
  87. package/src/cognitive/_ingest 2.py +0 -892
  88. package/src/cognitive/_memory 2.py +0 -912
  89. package/src/cognitive/_search 2.py +0 -949
  90. package/src/cognitive/_trust 2.py +0 -464
  91. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  92. package/src/crons/manifest 2.json +0 -106
  93. package/src/crons/sync 2.py +0 -217
  94. package/src/dashboard/__init__ 2.py +0 -0
  95. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  96. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  97. package/src/dashboard/app 2.py +0 -789
  98. package/src/db/__init__ 2.py +0 -89
  99. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  128. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  129. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  130. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  131. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  132. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  133. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  134. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  135. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  136. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  137. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  138. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  139. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  140. package/src/db/_core 2.py +0 -417
  141. package/src/db/_credentials 2.py +0 -124
  142. package/src/db/_entities 2.py +0 -178
  143. package/src/db/_episodic 2.py +0 -738
  144. package/src/db/_evolution 2.py +0 -54
  145. package/src/db/_fts 2.py +0 -406
  146. package/src/db/_learnings 2.py +0 -168
  147. package/src/db/_reminders 2.py +0 -338
  148. package/src/db/_schema 2.py +0 -364
  149. package/src/db/_sessions 2.py +0 -300
  150. package/src/db/_tasks 2.py +0 -91
  151. package/src/evolution_cycle 2.py +0 -266
  152. package/src/hnsw_index 2.py +0 -254
  153. package/src/hooks/auto_capture 2.py +0 -208
  154. package/src/hooks/caffeinate-guard 2.sh +0 -8
  155. package/src/hooks/capture-session 2.sh +0 -21
  156. package/src/hooks/capture-tool-logs 2.sh +0 -127
  157. package/src/hooks/daily-briefing-check 2.sh +0 -33
  158. package/src/hooks/inbox-hook 2.sh +0 -76
  159. package/src/hooks/post-compact 2.sh +0 -148
  160. package/src/hooks/pre-compact 2.sh +0 -151
  161. package/src/hooks/session-start 2.sh +0 -268
  162. package/src/hooks/session-stop 2.sh +0 -140
  163. package/src/kg_populate 2.py +0 -290
  164. package/src/knowledge_graph 2.py +0 -257
  165. package/src/maintenance 2.py +0 -59
  166. package/src/migrate_embeddings 2.py +0 -122
  167. package/src/plugin_loader 2.py +0 -202
  168. package/src/plugins/__init__ 2.py +0 -0
  169. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  172. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  175. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  189. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  191. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  193. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  194. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  195. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  196. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  197. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  198. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  199. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  200. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  201. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  202. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  203. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  204. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  205. package/src/plugins/adaptive_mode 2.py +0 -805
  206. package/src/plugins/agents 2.py +0 -52
  207. package/src/plugins/artifact_registry 2.py +0 -450
  208. package/src/plugins/backup 2.py +0 -104
  209. package/src/plugins/cognitive_memory 2.py +0 -564
  210. package/src/plugins/core_rules 2.py +0 -252
  211. package/src/plugins/cortex 2.py +0 -299
  212. package/src/plugins/entities 2.py +0 -67
  213. package/src/plugins/episodic_memory 2.py +0 -533
  214. package/src/plugins/evolution 2.py +0 -115
  215. package/src/plugins/guard 2.py +0 -746
  216. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  217. package/src/plugins/preferences 2.py +0 -47
  218. package/src/plugins/update 2.py +0 -256
  219. package/src/requirements 2.txt +0 -12
  220. package/src/rules/__init__ 2.py +0 -0
  221. package/src/rules/core-rules 2.json +0 -331
  222. package/src/rules/migrate 2.py +0 -207
  223. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  244. package/src/scripts/check-context 2.py +0 -264
  245. package/src/scripts/nexo-auto-update 2.py +0 -6
  246. package/src/scripts/nexo-backup 2.sh +0 -25
  247. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  248. package/src/scripts/nexo-catchup 2.py +0 -242
  249. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  250. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  251. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  252. package/src/scripts/nexo-evolution-run 2.py +0 -597
  253. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  254. package/src/scripts/nexo-github-monitor 2.py +0 -256
  255. package/src/scripts/nexo-immune 2.py +0 -927
  256. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  257. package/src/scripts/nexo-install 2.py +0 -6
  258. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  259. package/src/scripts/nexo-learning-validator 2.py +0 -207
  260. package/src/scripts/nexo-migrate 2.py +0 -232
  261. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  262. package/src/scripts/nexo-pre-commit 2.py +0 -120
  263. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  264. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  265. package/src/scripts/nexo-reflection 2.py +0 -253
  266. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  267. package/src/scripts/nexo-send-email 2.py +0 -25
  268. package/src/scripts/nexo-send-email.py +0 -25
  269. package/src/scripts/nexo-send-reply 2.py +0 -178
  270. package/src/scripts/nexo-send-reply.py +0 -178
  271. package/src/scripts/nexo-sleep 2.py +0 -592
  272. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  273. package/src/scripts/nexo-synthesis 2.py +0 -253
  274. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  275. package/src/scripts/nexo-update 2.sh +0 -161
  276. package/src/scripts/nexo-watchdog 2.sh +0 -878
  277. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  278. package/src/server 2.py +0 -733
  279. package/src/storage_router 2.py +0 -32
  280. package/src/tools_coordination 2.py +0 -102
  281. package/src/tools_credentials 2.py +0 -68
  282. package/src/tools_learnings 2.py +0 -220
  283. package/src/tools_menu 2.py +0 -227
  284. package/src/tools_reminders 2.py +0 -86
  285. package/src/tools_reminders_crud 2.py +0 -159
  286. package/src/tools_sessions 2.py +0 -476
  287. package/src/tools_task_history 2.py +0 -57
  288. package/templates/CLAUDE.md 2.template +0 -63
  289. package/templates/openclaw 2.json +0 -13
  290. package/tests/__init__ 2.py +0 -0
  291. package/tests/__init__.py +0 -0
  292. package/tests/conftest 2.py +0 -71
  293. package/tests/conftest.py +0 -71
  294. package/tests/test_cognitive 2.py +0 -205
  295. package/tests/test_cognitive.py +0 -205
  296. package/tests/test_knowledge_graph 2.py +0 -140
  297. package/tests/test_knowledge_graph.py +0 -140
  298. package/tests/test_migrations 2.py +0 -137
  299. 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.