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,208 +0,0 @@
1
- #!/usr/bin/env python3
2
- """NEXO Auto-Capture Hook — Extract facts from conversation context.
3
-
4
- Inspired by claude-mem's observation handler and transcript processor.
5
- Uses simple heuristics (no LLM) to extract decisions, corrections,
6
- and explicit facts from conversation messages.
7
-
8
- Can be called:
9
- - Programmatically via process_conversation()
10
- - From Claude Code hooks via stdin (pipe conversation lines)
11
- - As CLI: python3 auto_capture.py "message1" "message2" ...
12
-
13
- Stores extracted facts via cognitive.ingest() with appropriate tags.
14
- """
15
-
16
- import re
17
- import sys
18
- from pathlib import Path
19
-
20
- # Add source dir to path for cognitive imports
21
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
22
- import cognitive
23
-
24
-
25
- # ---------------------------------------------------------------------------
26
- # Pattern definitions (adapted from claude-mem's transcript processor
27
- # and ShieldCortex's pattern groups approach)
28
- # ---------------------------------------------------------------------------
29
-
30
- # Decision patterns — lines indicating a choice was made
31
- _DECISION_PATTERNS = [
32
- re.compile(r'\b(?:decided|agreed|will do|changed to|switching to|going with|chose|chosen|opted for)\b', re.IGNORECASE),
33
- re.compile(r'\b(?:let\'?s go with|the plan is|we\'?ll use|moving forward with)\b', re.IGNORECASE),
34
- re.compile(r'\b(?:approved|confirmed|locked in|finalized)\b', re.IGNORECASE),
35
- re.compile(r'\b(?:decidido|acordado|vamos con|cambiamos a|elegimos)\b', re.IGNORECASE), # Spanish
36
- ]
37
-
38
- # Correction patterns — lines indicating something was wrong
39
- _CORRECTION_PATTERNS = [
40
- re.compile(r'\b(?:don\'?t|stop|wrong|incorrect|that\'?s not right|fix this)\b', re.IGNORECASE),
41
- re.compile(r'\b(?:should be|actually|not that|the correct|mistake|error)\b', re.IGNORECASE),
42
- re.compile(r'\b(?:never do that|wrong approach|that broke|revert)\b', re.IGNORECASE),
43
- re.compile(r'\b(?:no,\s|nope|mal|otra vez|ya te dije|no es|est[aá] mal)\b', re.IGNORECASE), # Spanish
44
- ]
45
-
46
- # Explicit fact patterns — user explicitly asks to remember something
47
- _EXPLICIT_PATTERNS = [
48
- re.compile(r'\b(?:remember|note that|important:|keep in mind|don\'?t forget)\b', re.IGNORECASE),
49
- re.compile(r'\b(?:for future reference|take note|key point|rule:)\b', re.IGNORECASE),
50
- re.compile(r'\b(?:recuerda|importante:|ten en cuenta|no olvides|regla:)\b', re.IGNORECASE), # Spanish
51
- ]
52
-
53
- # Minimum line length to consider (skip very short lines)
54
- _MIN_LINE_LENGTH = 15
55
-
56
- # Maximum fact content length
57
- _MAX_FACT_LENGTH = 500
58
-
59
-
60
- def _classify_line(line: str) -> list[tuple[str, str]]:
61
- """Classify a single line into fact types.
62
-
63
- Returns list of (fact_type, content) tuples. A line can match
64
- multiple categories.
65
- """
66
- line = line.strip()
67
- if len(line) < _MIN_LINE_LENGTH:
68
- return []
69
-
70
- facts = []
71
-
72
- for pattern in _DECISION_PATTERNS:
73
- if pattern.search(line):
74
- facts.append(("decision", line))
75
- break
76
-
77
- for pattern in _CORRECTION_PATTERNS:
78
- if pattern.search(line):
79
- facts.append(("correction", line))
80
- break
81
-
82
- for pattern in _EXPLICIT_PATTERNS:
83
- if pattern.search(line):
84
- facts.append(("explicit", line))
85
- break
86
-
87
- return facts
88
-
89
-
90
- def process_conversation(messages: list[str]) -> dict:
91
- """Process conversation messages and extract key facts.
92
-
93
- Adapted from claude-mem's TranscriptEventProcessor: scans each message
94
- line for decision, correction, and explicit fact patterns. Stores
95
- extracted facts via cognitive.ingest() with source_type='auto_capture'.
96
-
97
- Args:
98
- messages: List of conversation message strings
99
-
100
- Returns:
101
- Dict with facts_extracted, decisions, corrections, stored,
102
- rejected_by_gate counts and extracted_facts details.
103
- """
104
- all_facts = []
105
- decisions = 0
106
- corrections = 0
107
- explicits = 0
108
-
109
- for msg in messages:
110
- # Split message into lines and classify each
111
- for line in msg.split("\n"):
112
- classified = _classify_line(line)
113
- for fact_type, content in classified:
114
- if fact_type == "decision":
115
- decisions += 1
116
- elif fact_type == "correction":
117
- corrections += 1
118
- elif fact_type == "explicit":
119
- explicits += 1
120
- all_facts.append((fact_type, content[:_MAX_FACT_LENGTH]))
121
-
122
- # Deduplicate by content (same line might appear in multiple messages)
123
- seen = set()
124
- unique_facts = []
125
- for fact_type, content in all_facts:
126
- content_key = content.lower().strip()
127
- if content_key not in seen:
128
- seen.add(content_key)
129
- unique_facts.append((fact_type, content))
130
-
131
- # Store via cognitive.ingest()
132
- stored = 0
133
- rejected_by_gate = 0
134
- extracted_details = []
135
-
136
- for fact_type, content in unique_facts:
137
- # Build tagged content for better retrieval
138
- tagged_content = f"[{fact_type.upper()}] {content}"
139
-
140
- result_id = cognitive.ingest(
141
- content=tagged_content,
142
- source_type="auto_capture",
143
- source_id=f"hook_{fact_type}",
144
- source_title=f"Auto-captured {fact_type}",
145
- domain="conversation",
146
- source="agent_observation",
147
- skip_quarantine=False, # Route through quarantine for safety
148
- bypass_gate=False, # Let prediction error gate filter duplicates
149
- )
150
-
151
- if result_id == 0:
152
- rejected_by_gate += 1
153
- else:
154
- stored += 1
155
-
156
- extracted_details.append({
157
- "type": fact_type,
158
- "content": content[:100],
159
- "stored": result_id != 0,
160
- "memory_id": result_id,
161
- })
162
-
163
- return {
164
- "facts_extracted": len(unique_facts),
165
- "decisions": decisions,
166
- "corrections": corrections,
167
- "explicits": explicits,
168
- "stored": stored,
169
- "rejected_by_gate": rejected_by_gate,
170
- "extracted_facts": extracted_details,
171
- }
172
-
173
-
174
- def _read_stdin() -> list[str]:
175
- """Read conversation lines from stdin (for hook integration)."""
176
- if sys.stdin.isatty():
177
- return []
178
- return [line for line in sys.stdin.read().strip().split("\n") if line.strip()]
179
-
180
-
181
- def main():
182
- """CLI entry point — accepts messages as args or from stdin.
183
-
184
- Usage:
185
- echo "We decided to use PostgreSQL" | python3 auto_capture.py
186
- python3 auto_capture.py "Remember: always use WAL mode" "That's wrong, fix it"
187
- """
188
- messages = list(sys.argv[1:]) if len(sys.argv) > 1 else _read_stdin()
189
-
190
- if not messages:
191
- print("Usage: python3 auto_capture.py 'message1' 'message2' ...")
192
- print(" or: echo 'messages' | python3 auto_capture.py")
193
- sys.exit(1)
194
-
195
- result = process_conversation(messages)
196
- print(f"Facts extracted: {result['facts_extracted']}")
197
- print(f" Decisions: {result['decisions']}")
198
- print(f" Corrections: {result['corrections']}")
199
- print(f" Explicits: {result['explicits']}")
200
- print(f"Stored: {result['stored']}, Rejected by gate: {result['rejected_by_gate']}")
201
-
202
- for fact in result["extracted_facts"]:
203
- status = "STORED" if fact["stored"] else "REJECTED"
204
- print(f" [{status}] [{fact['type']}] {fact['content']}")
205
-
206
-
207
- if __name__ == "__main__":
208
- main()
@@ -1,8 +0,0 @@
1
- #!/bin/bash
2
- # NEXO Caffeinate Guard — keeps the Mac awake so nocturnal processes run on schedule.
3
- # Runs as a LaunchAgent with KeepAlive=true. If killed, launchd restarts it.
4
- #
5
- # Uses caffeinate -s (prevent system sleep) with -i (prevent idle sleep).
6
- # The Mac screen can turn off but the system stays awake.
7
-
8
- exec caffeinate -s -i -w $$
@@ -1,21 +0,0 @@
1
- #!/bin/bash
2
- # NEXO PostToolUse hook — captures tool usage to session_buffer.jsonl
3
- # This feeds the Sensory Register (Atkinson-Shiffrin Layer 1)
4
-
5
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
6
- BUFFER="$NEXO_HOME/brain/session_buffer.jsonl"
7
-
8
- mkdir -p "$NEXO_HOME/brain"
9
-
10
- # Capture basic event: timestamp + tool name
11
- # Read stdin (Claude Code passes JSON via stdin for PostToolUse hooks)
12
- INPUT=$(cat 2>/dev/null || true)
13
- TOOL_NAME="${CLAUDE_TOOL_NAME:-unknown}"
14
- TS=$(date -u +"%Y-%m-%dT%H:%M:%S")
15
-
16
- # Only log meaningful tool calls (skip reads, globs, greps)
17
- case "$TOOL_NAME" in
18
- Read|Glob|Grep|LS|Bash) exit 0 ;;
19
- esac
20
-
21
- echo "{\"ts\":\"$TS\",\"tool\":\"$TOOL_NAME\",\"source\":\"hook\"}" >> "$BUFFER"
@@ -1,127 +0,0 @@
1
- #!/bin/bash
2
- # NEXO PostToolUse hook — persists tool call outputs to daily JSONL logs
3
- # Fires automatically after every successful or failed tool use.
4
- # Logs survive context compactions.
5
- # Auto-cleanup: deletes logs >= 30 days old.
6
- # Optimized: skips read-only tools (Read, Grep, Glob, LS, Skill, ToolSearch).
7
-
8
- # Read full JSON from stdin first
9
- INPUT=$(cat || true)
10
- [ -z "$INPUT" ] && exit 0
11
-
12
- # Extract tool_name early and exit if read-only (avoids overhead on 90%+ of calls)
13
- TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || true)
14
-
15
- case "$TOOL_NAME" in
16
- Read|Grep|Glob|LS|Skill|ToolSearch) exit 0 ;;
17
- esac
18
-
19
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
20
- LOG_DIR="$NEXO_HOME/operations/tool-logs"
21
- mkdir -p "$LOG_DIR"
22
-
23
- TODAY=$(date +%Y-%m-%d)
24
- LOG_FILE="$LOG_DIR/${TODAY}.jsonl"
25
-
26
- # Build and write record with python3 (faster than jq on macOS when cached)
27
- echo "$INPUT" | python3 -c "
28
- import json, sys
29
- from datetime import datetime
30
- d = json.load(sys.stdin)
31
- record = {
32
- 'timestamp': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
33
- 'session_id': d.get('session_id', 'unknown'),
34
- 'tool_name': d.get('tool_name', 'unknown'),
35
- 'hook_event': d.get('hook_event_name', 'unknown'),
36
- 'tool_use_id': d.get('tool_use_id'),
37
- 'tool_input': d.get('tool_input'),
38
- 'tool_response': d.get('tool_response'),
39
- 'error': d.get('error')
40
- }
41
- print(json.dumps(record))
42
- " >> "$LOG_FILE" 2>/dev/null
43
-
44
- # ── Layer 1: Auto-diary every 10 tool calls ─────────────────────────
45
- COUNTER_FILE="$NEXO_HOME/operations/.tool-call-count"
46
- NEXO_DB="$NEXO_HOME/data/nexo.db"
47
-
48
- # Increment counter (atomic: read+write in one step)
49
- COUNT=1
50
- if [ -f "$COUNTER_FILE" ]; then
51
- COUNT=$(( $(cat "$COUNTER_FILE" 2>/dev/null || echo 0) + 1 ))
52
- fi
53
- echo "$COUNT" > "$COUNTER_FILE"
54
-
55
- # Every 10 tool calls, write a mechanical diary draft to SQLite
56
- if [ $(( COUNT % 10 )) -eq 0 ] && [ -f "$NEXO_DB" ]; then
57
- python3 -c "
58
- import json, sqlite3, os, sys
59
- from datetime import datetime
60
-
61
- db_path = '$NEXO_DB'
62
- log_file = '$LOG_FILE'
63
- count = $COUNT
64
-
65
- # Read last 10 tool calls from today's log
66
- entries = []
67
- if os.path.isfile(log_file):
68
- with open(log_file, 'r') as f:
69
- lines = f.readlines()
70
- for line in lines[-10:]:
71
- try:
72
- e = json.loads(line.strip())
73
- name = e.get('tool_name', '?')
74
- inp = e.get('tool_input', {})
75
- # Brief args: first key's value, truncated
76
- brief = ''
77
- if isinstance(inp, dict):
78
- for k, v in list(inp.items())[:1]:
79
- brief = str(v)[:60]
80
- entries.append(f'{name}({brief})')
81
- except Exception:
82
- pass
83
-
84
- if not entries:
85
- sys.exit(0)
86
-
87
- tools_summary = ', '.join(entries[-10:])
88
-
89
- # Get current session and task from sessions table
90
- conn = sqlite3.connect(db_path, timeout=2)
91
- conn.row_factory = sqlite3.Row
92
- row = conn.execute(
93
- 'SELECT sid, task FROM sessions ORDER BY last_update_epoch DESC LIMIT 1'
94
- ).fetchone()
95
- if not row:
96
- conn.close()
97
- sys.exit(0)
98
-
99
- sid = row['sid']
100
- task = row['task'] or 'unknown'
101
-
102
- summary = f'[AUTO-{count}] {len(entries)} tool calls: {tools_summary[:250]}. Task: {task[:100]}'
103
-
104
- # Write to session_diary_draft (UPSERT)
105
- conn.execute('''
106
- INSERT INTO session_diary_draft (sid, summary_draft, tasks_seen, change_ids, decision_ids, last_context_hint, heartbeat_count, updated_at)
107
- VALUES (?, ?, '[]', '[]', '[]', ?, 0, datetime('now'))
108
- ON CONFLICT(sid) DO UPDATE SET
109
- summary_draft = excluded.summary_draft,
110
- last_context_hint = excluded.last_context_hint,
111
- updated_at = datetime('now')
112
- ''', (sid, summary, f'auto-diary at {count} tool calls'))
113
- conn.commit()
114
- conn.close()
115
- " 2>/dev/null &
116
- # Reset counter after writing
117
- echo "0" > "$COUNTER_FILE"
118
- fi
119
-
120
- # Cleanup: delete logs >= 30 days old (once daily, uses marker file)
121
- CLEANUP_MARKER="$LOG_DIR/.last-cleanup"
122
- if [ ! -f "$CLEANUP_MARKER" ] || [ "$(cat "$CLEANUP_MARKER" 2>/dev/null)" != "$TODAY" ]; then
123
- find "$LOG_DIR" -name "*.jsonl" -mtime +30 -delete 2>/dev/null || true
124
- echo "$TODAY" > "$CLEANUP_MARKER"
125
- fi
126
-
127
- exit 0
@@ -1,33 +0,0 @@
1
- #!/bin/bash
2
- # NEXO Daily Briefing — SessionStart hook
3
- # Checks if a briefing should be sent and creates a flag for NEXO to process.
4
- # Does NOT send the email directly (needs Claude to research news).
5
- # Only marks that NEXO should launch the briefing at startup.
6
- # Frequency: Monday, Wednesday, Friday (3x/week)
7
-
8
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
9
- BRIEFING_FILE="$NEXO_HOME/operations/.briefing-last-sent"
10
- FLAG_FILE="$NEXO_HOME/operations/.briefing-pending"
11
- TODAY=$(date +%Y-%m-%d)
12
- HOUR=$(date +%H)
13
- DOW=$(date +%u) # 1=Monday, 7=Sunday
14
-
15
- # Only after 8:00 AM — before that counts as "previous day"
16
- if [ "$HOUR" -lt 8 ]; then
17
- exit 0
18
- fi
19
-
20
- # Only Monday (1), Wednesday (3), Friday (5)
21
- if [ "$DOW" != "1" ] && [ "$DOW" != "3" ] && [ "$DOW" != "5" ]; then
22
- exit 0
23
- fi
24
-
25
- # If already sent today, skip
26
- LAST_SENT=$(cat "$BRIEFING_FILE" 2>/dev/null)
27
- if [ "$LAST_SENT" = "$TODAY" ]; then
28
- exit 0
29
- fi
30
-
31
- # Mark briefing as pending for NEXO to launch in background
32
- echo "$TODAY" > "$FLAG_FILE"
33
- echo "briefing-pending"
@@ -1,76 +0,0 @@
1
- #!/bin/bash
2
- # NEXO PostToolUse hook — automatic inter-terminal inbox check
3
- #
4
- # Zero output when no messages = zero tokens consumed in Claude's context.
5
- # Reads SQLite directly (no MCP overhead). Write-only: INSERT OR IGNORE for mark-as-read.
6
- # Debounce: skips if last check was <2 seconds ago.
7
-
8
- INPUT=$(cat || true)
9
- [ -z "$INPUT" ] && exit 0
10
-
11
- # 1. Skip read-only tools (same logic as capture-tool-logs.sh)
12
- TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || true)
13
- case "$TOOL_NAME" in
14
- Read|Grep|Glob|LS|Skill|ToolSearch|Agent) exit 0 ;;
15
- esac
16
-
17
- # 2. Extract Claude Code session_id
18
- CLAUDE_SID=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" 2>/dev/null)
19
- [ -z "$CLAUDE_SID" ] && exit 0
20
-
21
- # 3. Debounce: skip if last check <2s ago
22
- DEBOUNCE_FILE="/tmp/nexo-inbox-ts-${CLAUDE_SID}"
23
- NOW=$(date +%s)
24
- LAST=$(cat "$DEBOUNCE_FILE" 2>/dev/null || echo 0)
25
- DIFF=$((NOW - LAST))
26
- [ "$DIFF" -lt 2 ] && exit 0
27
- echo "$NOW" > "$DEBOUNCE_FILE"
28
-
29
- # 4. Find NEXO SID mapped to this Claude session_id
30
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
31
- DB="$NEXO_HOME/data/nexo.db"
32
- mkdir -p "$NEXO_HOME/data"
33
- [ -f "$DB" ] || exit 0
34
-
35
- NEXO_SID=$(sqlite3 "$DB" "SELECT sid FROM sessions WHERE claude_session_id = '${CLAUDE_SID}' AND last_update_epoch > (strftime('%s','now') - 900) ORDER BY last_update_epoch DESC LIMIT 1;" 2>/dev/null)
36
- [ -z "$NEXO_SID" ] && exit 0
37
-
38
- # 5. Check inbox — messages addressed to this session or broadcast
39
- MESSAGES=$(sqlite3 -separator '|' "$DB" "
40
- SELECT m.id, m.from_sid, m.text FROM messages m
41
- WHERE (m.to_sid = 'all' OR m.to_sid = '${NEXO_SID}')
42
- AND m.from_sid != '${NEXO_SID}'
43
- AND m.id NOT IN (SELECT message_id FROM message_reads WHERE sid = '${NEXO_SID}')
44
- LIMIT 5;
45
- " 2>/dev/null)
46
-
47
- # 6. Check pending questions
48
- QUESTIONS=$(sqlite3 -separator '|' "$DB" "
49
- SELECT qid, from_sid, question FROM questions
50
- WHERE to_sid = '${NEXO_SID}' AND answer IS NULL
51
- LIMIT 3;
52
- " 2>/dev/null)
53
-
54
- # 7. If empty -> silent exit (0 tokens consumed)
55
- [ -z "$MESSAGES" ] && [ -z "$QUESTIONS" ] && exit 0
56
-
57
- # 8. Format and output (injected into Claude's context)
58
- echo ""
59
- echo "INTER-TERMINAL MESSAGE (auto-detected):"
60
-
61
- if [ -n "$MESSAGES" ]; then
62
- echo "$MESSAGES" | while IFS='|' read -r mid from text; do
63
- echo " [$from]: $text"
64
- # Mark as read (lightweight INSERT, WAL mode, no lock contention)
65
- sqlite3 "$DB" "INSERT OR IGNORE INTO message_reads (message_id, sid) VALUES ('${mid}', '${NEXO_SID}');" 2>/dev/null
66
- done
67
- fi
68
-
69
- if [ -n "$QUESTIONS" ]; then
70
- echo " PENDING QUESTIONS from another terminal — respond with nexo_answer:"
71
- echo "$QUESTIONS" | while IFS='|' read -r qid from question; do
72
- echo " Q[$qid] from [$from]: $question"
73
- done
74
- fi
75
-
76
- exit 0
@@ -1,148 +0,0 @@
1
- #!/bin/bash
2
- # NEXO PostCompact Hook — Re-inject Core Memory Block after compaction
3
- # Reads the latest session checkpoint from SQLite and generates a structured
4
- # context block that preserves session continuity.
5
- set -uo pipefail
6
-
7
- NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
8
- NEXO_DB="$NEXO_HOME/data/nexo.db"
9
- mkdir -p "$NEXO_HOME/data"
10
- TODAY=$(date +%Y-%m-%d)
11
- LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
12
- LOG_LINES=0
13
- if [ -f "$LOG_FILE" ]; then
14
- LOG_LINES=$(wc -l < "$LOG_FILE" | tr -d ' ')
15
- fi
16
-
17
- # Read checkpoint for the session that just compacted
18
- # PreCompact writes the SID to /tmp/nexo-compacting-sid
19
- TARGET_SID=""
20
- if [ -f /tmp/nexo-compacting-sid ]; then
21
- TARGET_SID=$(cat /tmp/nexo-compacting-sid 2>/dev/null || echo "")
22
- rm -f /tmp/nexo-compacting-sid
23
- fi
24
-
25
- CHECKPOINT=""
26
- if [ -f "$NEXO_DB" ]; then
27
- if [ -n "$TARGET_SID" ]; then
28
- # Read checkpoint for the specific session that compacted
29
- CHECKPOINT=$(sqlite3 "$NEXO_DB" "
30
- SELECT sid, task, task_status, active_files, current_goal,
31
- decisions_summary, errors_found, reasoning_thread,
32
- next_step, compaction_count
33
- FROM session_checkpoints
34
- WHERE sid = '$TARGET_SID'
35
- " 2>/dev/null || echo "")
36
- fi
37
- # Fallback: if no target SID or no checkpoint found, use latest
38
- if [ -z "$CHECKPOINT" ]; then
39
- CHECKPOINT=$(sqlite3 "$NEXO_DB" "
40
- SELECT sid, task, task_status, active_files, current_goal,
41
- decisions_summary, errors_found, reasoning_thread,
42
- next_step, compaction_count
43
- FROM session_checkpoints
44
- ORDER BY updated_at DESC LIMIT 1
45
- " 2>/dev/null || echo "")
46
- fi
47
-
48
- if [ -n "$CHECKPOINT" ]; then
49
- # Parse pipe-separated fields
50
- SID=$(echo "$CHECKPOINT" | cut -d'|' -f1)
51
- TASK=$(echo "$CHECKPOINT" | cut -d'|' -f2)
52
- TASK_STATUS=$(echo "$CHECKPOINT" | cut -d'|' -f3)
53
- ACTIVE_FILES=$(echo "$CHECKPOINT" | cut -d'|' -f4)
54
- CURRENT_GOAL=$(echo "$CHECKPOINT" | cut -d'|' -f5)
55
- DECISIONS=$(echo "$CHECKPOINT" | cut -d'|' -f6)
56
- ERRORS=$(echo "$CHECKPOINT" | cut -d'|' -f7)
57
- REASONING=$(echo "$CHECKPOINT" | cut -d'|' -f8)
58
- NEXT_STEP=$(echo "$CHECKPOINT" | cut -d'|' -f9)
59
- COMPACT_COUNT=$(echo "$CHECKPOINT" | cut -d'|' -f10)
60
-
61
- # Increment compaction count
62
- sqlite3 "$NEXO_DB" "
63
- UPDATE session_checkpoints
64
- SET compaction_count = compaction_count + 1, updated_at = datetime('now')
65
- WHERE sid = '$SID'
66
- " 2>/dev/null || true
67
-
68
- # Read diary draft for extra context
69
- DRAFT=$(sqlite3 "$NEXO_DB" "
70
- SELECT tasks_seen, last_context_hint
71
- FROM session_diary_draft
72
- WHERE sid = '$SID'
73
- " 2>/dev/null || echo "")
74
-
75
- TASKS_SEEN=""
76
- LAST_HINT=""
77
- if [ -n "$DRAFT" ]; then
78
- TASKS_SEEN=$(echo "$DRAFT" | cut -d'|' -f1)
79
- LAST_HINT=$(echo "$DRAFT" | cut -d'|' -f2)
80
- fi
81
-
82
- # Build Core Memory Block
83
- BLOCK="## SESSION CONTINUITY [auto-injected post-compaction #$((COMPACT_COUNT + 1))]"
84
- BLOCK="$BLOCK\n**Session:** $SID"
85
- BLOCK="$BLOCK\n**Task:** $TASK (status: $TASK_STATUS)"
86
-
87
- if [ -n "$CURRENT_GOAL" ] && [ "$CURRENT_GOAL" != "$TASK" ]; then
88
- BLOCK="$BLOCK\n**Goal:** $CURRENT_GOAL"
89
- fi
90
-
91
- if [ -n "$ACTIVE_FILES" ] && [ "$ACTIVE_FILES" != "[]" ]; then
92
- BLOCK="$BLOCK\n**Files:** $ACTIVE_FILES"
93
- fi
94
-
95
- if [ -n "$DECISIONS" ]; then
96
- BLOCK="$BLOCK\n**Decisions:** $DECISIONS"
97
- fi
98
-
99
- if [ -n "$ERRORS" ]; then
100
- BLOCK="$BLOCK\n**Errors:** $ERRORS"
101
- fi
102
-
103
- if [ -n "$NEXT_STEP" ]; then
104
- BLOCK="$BLOCK\n**Next:** $NEXT_STEP"
105
- fi
106
-
107
- if [ -n "$REASONING" ]; then
108
- BLOCK="$BLOCK\n**Context:** $REASONING"
109
- fi
110
-
111
- if [ -n "$LAST_HINT" ]; then
112
- BLOCK="$BLOCK\n**Last context:** $LAST_HINT"
113
- fi
114
-
115
- if [ -n "$TASKS_SEEN" ] && [ "$TASKS_SEEN" != "[]" ]; then
116
- BLOCK="$BLOCK\n**Session tasks so far:** $TASKS_SEEN"
117
- fi
118
-
119
- BLOCK="$BLOCK\n**Tool logs:** ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl ($LOG_LINES entries)"
120
- BLOCK="$BLOCK\n\n**POST-COMPACTION INSTRUCTIONS:**"
121
- BLOCK="$BLOCK\n1. Call nexo_heartbeat with the SID above to reconnect with the session"
122
- BLOCK="$BLOCK\n2. If you need specific lost data, query tool logs with jq"
123
- BLOCK="$BLOCK\n3. Continue the task from where it left off — do NOT start from zero"
124
- BLOCK="$BLOCK\n4. MCP tools (nexo_*) have all persistent state"
125
-
126
- # Escape for JSON
127
- BLOCK_ESCAPED=$(echo -e "$BLOCK" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
128
-
129
- cat << HOOKEOF
130
- {
131
- "systemMessage": $BLOCK_ESCAPED
132
- }
133
- HOOKEOF
134
- else
135
- # No checkpoint — fallback to basic message
136
- cat << 'HOOKEOF'
137
- {
138
- "systemMessage": "Post-compaction: no prior checkpoint found. Call nexo_heartbeat to reconnect session state."
139
- }
140
- HOOKEOF
141
- fi
142
- else
143
- cat << 'HOOKEOF'
144
- {
145
- "systemMessage": "Post-compaction: nexo.db not found. Reconnect via nexo_startup."
146
- }
147
- HOOKEOF
148
- fi