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,476 +0,0 @@
1
- """Session management tools: startup, heartbeat, status."""
2
-
3
- import time
4
- import secrets
5
- import threading
6
- from db import (
7
- register_session, update_session, complete_session,
8
- get_active_sessions, clean_stale_sessions, search_sessions,
9
- get_inbox, get_pending_questions, now_epoch,
10
- SESSION_STALE_SECONDS, check_session_has_diary,
11
- save_checkpoint, read_checkpoint, increment_compaction_count,
12
- )
13
-
14
- # ── Session Keepalive ────────────────────────────────────────────────
15
- # Background thread per session that auto-pings last_update_epoch every
16
- # KEEPALIVE_INTERVAL seconds. This prevents clean_stale_sessions from
17
- # killing sessions that are alive but quiet (e.g. waiting on long Tasks).
18
- # Threads are daemon=True so they die when the MCP server process exits.
19
-
20
- KEEPALIVE_INTERVAL = 600 # 10 min — well inside the 15-min TTL
21
-
22
- _keepalive_threads: dict[str, threading.Event] = {} # sid → stop_event
23
-
24
-
25
- def _keepalive_loop(sid: str, stop_event: threading.Event) -> None:
26
- """Periodically touch the session's last_update_epoch until stopped."""
27
- while not stop_event.wait(KEEPALIVE_INTERVAL):
28
- try:
29
- update_session(sid, None) # None = keep current task, just touch timestamp
30
- except Exception:
31
- break # DB gone or session deleted — exit silently
32
-
33
-
34
- def _start_keepalive(sid: str) -> None:
35
- """Start a keepalive thread for the given session."""
36
- _stop_keepalive(sid) # clean up any leftover
37
- stop_event = threading.Event()
38
- _keepalive_threads[sid] = stop_event
39
- t = threading.Thread(target=_keepalive_loop, args=(sid, stop_event), daemon=True)
40
- t.start()
41
-
42
-
43
- def _stop_keepalive(sid: str) -> None:
44
- """Signal the keepalive thread for the given session to stop."""
45
- stop_event = _keepalive_threads.pop(sid, None)
46
- if stop_event is not None:
47
- stop_event.set()
48
-
49
-
50
- def _generate_sid() -> str:
51
- """Generate unique session ID: nexo-{epoch}-{random}."""
52
- return f"nexo-{int(time.time())}-{secrets.randbelow(100000)}"
53
-
54
-
55
- def _format_age(epoch: float) -> str:
56
- """Format seconds since epoch as human-readable age."""
57
- seconds = now_epoch() - epoch
58
- if seconds < 60:
59
- return f"{int(seconds)}s"
60
- elif seconds < 3600:
61
- return f"{int(seconds / 60)}m"
62
- else:
63
- return f"{int(seconds / 3600)}h{int((seconds % 3600) / 60)}m"
64
-
65
-
66
- def handle_startup(task: str = "Startup", claude_session_id: str = "") -> str:
67
- """Full startup sequence: register, clean, report.
68
-
69
- Args:
70
- task: Initial task description
71
- claude_session_id: UUID from Claude Code (passed via SessionStart hook file).
72
- Enables automatic inbox detection via PostToolUse hook.
73
- """
74
- sid = _generate_sid()
75
- cleaned = clean_stale_sessions()
76
- register_session(sid, task, claude_session_id=claude_session_id)
77
- _start_keepalive(sid)
78
- active = get_active_sessions()
79
- other_sessions = [s for s in active if s["sid"] != sid]
80
- inbox = get_inbox(sid)
81
-
82
- lines = [f"SID: {sid}"]
83
-
84
- if cleaned > 0:
85
- lines.append(f"Cleaned {cleaned} stale sessions.")
86
-
87
- if other_sessions:
88
- lines.append("")
89
- lines.append("ACTIVE SESSIONS:")
90
- for s in other_sessions:
91
- age = _format_age(s["last_update_epoch"])
92
- lines.append(f" {s['sid']} ({age}) — {s['task']}")
93
- else:
94
- lines.append("No other active sessions.")
95
-
96
- if inbox:
97
- lines.append("")
98
- lines.append("PENDING MESSAGES:")
99
- for m in inbox:
100
- age = _format_age(m["created_epoch"])
101
- lines.append(f" [{m['from_sid']}] ({age}): {m['text']}")
102
-
103
- return "\n".join(lines)
104
-
105
-
106
- def handle_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
107
- """Update session, check inbox + questions. Lightweight — no embeddings, no RAG.
108
-
109
- For cognitive features (sentiment, trust, RAG), use dedicated tools on-demand:
110
- - nexo_cognitive_sentiment (sentiment detection)
111
- - nexo_cognitive_trust (trust adjustment)
112
- - nexo_cognitive_retrieve / nexo_recall (memory retrieval)
113
- - nexo_context_packet (area-specific learnings)
114
-
115
- Args:
116
- sid: Session ID
117
- task: Current task description
118
- context_hint: Optional — stored for diary draft context, not processed.
119
- """
120
- from db import get_db
121
- update_session(sid, task)
122
- parts = [f"OK: {sid} — {task}"]
123
-
124
- inbox = get_inbox(sid)
125
- if inbox:
126
- parts.append("")
127
- parts.append("MESSAGES:")
128
- for m in inbox:
129
- age = _format_age(m["created_epoch"])
130
- parts.append(f" [{m['from_sid']}] ({age}): {m['text']}")
131
-
132
- questions = get_pending_questions(sid)
133
- if questions:
134
- parts.append("")
135
- parts.append("PENDING QUESTIONS (respond with nexo_answer):")
136
- for q in questions:
137
- age = _format_age(q["created_epoch"])
138
- parts.append(f" {q['qid']} de {q['from_sid']} ({age}): {q['question']}")
139
-
140
- # Incremental diary draft — accumulate every heartbeat, full UPSERT every 5
141
- _hb_count = 0 # Hoisted for Layer 3 DIARY_OVERDUE signal
142
- try:
143
- import json as _json
144
- from db import get_diary_draft, upsert_diary_draft
145
-
146
- draft = get_diary_draft(sid)
147
- hb_count = (draft["heartbeat_count"] + 1) if draft else 1
148
- _hb_count = hb_count # Copy to outer scope for Layer 3
149
-
150
- existing_tasks = _json.loads(draft["tasks_seen"]) if draft else []
151
- if task and task not in existing_tasks:
152
- existing_tasks.append(task)
153
-
154
- _conn = get_db()
155
- if hb_count % 5 == 0 or hb_count == 1:
156
- change_rows = _conn.execute(
157
- "SELECT id FROM change_log WHERE session_id = ? ORDER BY id", (sid,)
158
- ).fetchall()
159
- change_ids = [r["id"] for r in change_rows]
160
-
161
- decision_rows = _conn.execute(
162
- "SELECT id FROM decisions WHERE session_id = ? ORDER BY id", (sid,)
163
- ).fetchall()
164
- decision_ids = [r["id"] for r in decision_rows]
165
-
166
- summary = f"Session tasks: {', '.join(existing_tasks[-10:])}"
167
- upsert_diary_draft(
168
- sid=sid,
169
- tasks_seen=_json.dumps(existing_tasks),
170
- change_ids=_json.dumps(change_ids),
171
- decision_ids=_json.dumps(decision_ids),
172
- last_context_hint=context_hint[:300] if context_hint else '',
173
- heartbeat_count=hb_count,
174
- summary_draft=summary,
175
- )
176
- else:
177
- upsert_diary_draft(
178
- sid=sid,
179
- tasks_seen=_json.dumps(existing_tasks),
180
- change_ids=draft["change_ids"] if draft else '[]',
181
- decision_ids=draft["decision_ids"] if draft else '[]',
182
- last_context_hint=context_hint[:300] if context_hint else (draft["last_context_hint"] if draft else ''),
183
- heartbeat_count=hb_count,
184
- summary_draft=draft["summary_draft"] if draft else f"Session task: {task}",
185
- )
186
- except Exception:
187
- pass # Draft accumulation is best-effort, never block heartbeat
188
-
189
- # Update session checkpoint with current goal (lightweight, every heartbeat)
190
- try:
191
- save_checkpoint(
192
- sid=sid,
193
- task=task,
194
- current_goal=context_hint[:300] if context_hint else task,
195
- )
196
- except Exception:
197
- pass # Checkpoint update is best-effort
198
-
199
- # ── Layer 3: DIARY_OVERDUE signal based on heartbeat count + time ──
200
- conn = get_db()
201
- row = conn.execute("SELECT started_epoch FROM sessions WHERE sid = ?", (sid,)).fetchone()
202
- if row:
203
- age_seconds = now_epoch() - row["started_epoch"]
204
- has_diary = check_session_has_diary(sid)
205
-
206
- # DIARY_OVERDUE: >10 heartbeats OR >30 minutes, without a diary
207
- if not has_diary and (_hb_count > 10 or age_seconds >= 1800):
208
- parts.append("")
209
- parts.append(f"⚠ DIARY_OVERDUE: {_hb_count} heartbeats, {int(age_seconds/60)}min active, no diary. Write nexo_session_diary_write NOW.")
210
-
211
- return "\n".join(parts)
212
-
213
-
214
- def handle_context_packet(area: str, files: str = "") -> str:
215
- """Build a context packet for a specific area/project — designed for subagent injection.
216
-
217
- Returns: relevant learnings + last 5 changes + active followups + key preferences
218
- for the given area. Use this before delegating to a subagent.
219
-
220
- Args:
221
- area: Project/area name (e.g., 'ecommerce', 'shopify', 'backend', 'mobile-app', 'nexo')
222
- files: Optional comma-separated file paths for guard check
223
- """
224
- from db import get_db
225
- parts = []
226
-
227
- # 1. Learnings for this area (from nexo.db)
228
- conn = get_db()
229
- learnings = conn.execute(
230
- "SELECT id, title, content FROM learnings WHERE category LIKE ? OR content LIKE ? ORDER BY id DESC LIMIT 15",
231
- (f"%{area}%", f"%{area}%")
232
- ).fetchall()
233
- if learnings:
234
- parts.append("## KNOWN ERRORS — DO NOT REPEAT")
235
- for l in learnings:
236
- parts.append(f" L#{l['id']}: {l['title']}")
237
- # First 200 chars of content
238
- parts.append(f" {l['content'][:200]}")
239
- parts.append("")
240
-
241
- # 2. Last 5 changes in this area
242
- changes = conn.execute(
243
- "SELECT id, files, what_changed, why FROM change_log WHERE files LIKE ? OR what_changed LIKE ? ORDER BY id DESC LIMIT 5",
244
- (f"%{area}%", f"%{area}%")
245
- ).fetchall()
246
- if changes:
247
- parts.append("## RECENT CHANGES")
248
- for c in changes:
249
- parts.append(f" C#{c['id']}: {c['what_changed'][:150]}")
250
- if c['why']:
251
- parts.append(f" Why: {c['why'][:100]}")
252
- parts.append("")
253
-
254
- # 3. Active followups for this area
255
- followups = conn.execute(
256
- "SELECT id, description, date, verification FROM followups WHERE status = 'PENDING' AND (description LIKE ? OR verification LIKE ?) ORDER BY date ASC LIMIT 10",
257
- (f"%{area}%", f"%{area}%")
258
- ).fetchall()
259
- if followups:
260
- parts.append("## ACTIVE FOLLOWUPS")
261
- for f in followups:
262
- parts.append(f" {f['id']}: {f['description'][:150]} (date: {f['date']})")
263
- parts.append("")
264
-
265
- # 4. Preferences related to this area
266
- try:
267
- prefs = conn.execute(
268
- "SELECT key, value FROM preferences WHERE key LIKE ? OR value LIKE ? LIMIT 10",
269
- (f"%{area}%", f"%{area}%")
270
- ).fetchall()
271
- if prefs:
272
- parts.append("## PREFERENCES")
273
- for p in prefs:
274
- parts.append(f" {p['key']}: {p['value'][:150]}")
275
- parts.append("")
276
- except Exception:
277
- pass
278
-
279
- # 5. Cognitive memories for this area
280
- try:
281
- import cognitive
282
- results = cognitive.search(
283
- query_text=area,
284
- top_k=5,
285
- min_score=0.55,
286
- stores="ltm",
287
- rehearse=False,
288
- )
289
- if results:
290
- parts.append("## RELEVANT COGNITIVE MEMORIES")
291
- for r in results:
292
- parts.append(f" [{r['source_type']}] {r['source_title'] or r['content'][:80]}")
293
- parts.append("")
294
- except Exception:
295
- pass
296
-
297
- # 6. Data flow tracing requirement (mandatory for all subagents)
298
- parts.append("## MANDATORY RULE: DATA FLOW TRACING")
299
- parts.append("BEFORE modifying any file or data, answer these 3 questions:")
300
- parts.append(" 1. WHO PRODUCES this data? (which function/cron/endpoint generates it)")
301
- parts.append(" 2. WHO CONSUMES this data? (what other files/functions read it)")
302
- parts.append(" 3. WHAT BREAKS if I change it? (downstream effects)")
303
- parts.append("If you can't answer all 3 → READ the code that produces and consumes BEFORE touching.")
304
- parts.append("If you still can't → STOP and return the question. Do NOT guess.")
305
- parts.append("")
306
-
307
- if not parts:
308
- return f"No context found for area '{area}'. The subagent will start with no project-specific knowledge."
309
-
310
- header = f"CONTEXT PACKET — {area.upper()}\n{'='*40}\n\n"
311
- footer = f"\n{'='*40}\nINSTRUCTION: If you're not 100% sure about a fact, STOP and return the question. Do NOT invent."
312
- return header + "\n".join(parts) + footer
313
-
314
-
315
- def _load_session_tone() -> str | None:
316
- """Load session-tone.json generated by Deep Sleep and format as startup guidance.
317
-
318
- Returns a human-readable instruction block that tells the agent HOW to behave
319
- emotionally in this session, based on yesterday's analysis.
320
- """
321
- import os
322
- from pathlib import Path
323
- nexo_home = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
324
- tone_file = nexo_home / "operations" / "session-tone.json"
325
-
326
- if not tone_file.exists():
327
- return None
328
-
329
- try:
330
- import json
331
- tone = json.loads(tone_file.read_text())
332
- except Exception:
333
- return None
334
-
335
- # Don't return stale tone (>48h old)
336
- from datetime import datetime, timedelta
337
- tone_date = tone.get("date", "")
338
- if tone_date:
339
- try:
340
- td = datetime.strptime(tone_date, "%Y-%m-%d")
341
- if datetime.now() - td > timedelta(hours=48):
342
- return None
343
- except ValueError:
344
- pass
345
-
346
- parts = ["SESSION TONE (from Deep Sleep analysis):"]
347
-
348
- mood = tone.get("mood_yesterday", 0.5)
349
- approach = tone.get("approach", "neutral")
350
- parts.append(f" Yesterday mood: {mood:.0%} | Approach today: {approach}")
351
-
352
- if tone.get("acknowledge_mistakes"):
353
- mistakes = tone.get("mistakes_to_own", [])
354
- parts.append(f" ⚠ OWN YOUR MISTAKES: You made errors yesterday. Acknowledge them specifically:")
355
- for m in mistakes[:3]:
356
- parts.append(f" - {m[:100]}")
357
- parts.append(" Show what you learned. Don't just apologize — demonstrate improvement.")
358
-
359
- if tone.get("motivational"):
360
- if mood < 0.4:
361
- parts.append(" 💪 USER HAD A TOUGH DAY: Be supportive. Lighter start. Acknowledge difficulty.")
362
- else:
363
- parts.append(" 🚀 USER HAD A GREAT DAY: Reinforce momentum. Reference wins. Push ambitious goals.")
364
-
365
- if tone.get("reduce_load"):
366
- parts.append(" 📉 REDUCE LOAD: Don't overwhelm with tasks. Propose 1-2 key things, not a full agenda.")
367
-
368
- ctx = tone.get("suggested_greeting_context", "")
369
- if ctx:
370
- parts.append(f" Context: {ctx.strip()}")
371
-
372
- return "\n".join(parts) if len(parts) > 1 else None
373
-
374
-
375
- def handle_smart_startup_query() -> str:
376
- """Generate and execute a composite cognitive query from pending followups + diary topics + reminders.
377
-
378
- Called during startup to pre-load the most relevant context for this session.
379
- Returns cognitive memories that match the current operational state.
380
- """
381
- from db import get_db
382
- conn = get_db()
383
- query_parts = []
384
-
385
- # 1. Pending followups (what NEXO needs to do)
386
- followups = conn.execute(
387
- "SELECT description FROM followups WHERE status = 'PENDING' ORDER BY date ASC LIMIT 5"
388
- ).fetchall()
389
- for f in followups:
390
- query_parts.append(f['description'][:100])
391
-
392
- # 2. Due reminders (what the user needs to know)
393
- reminders = conn.execute(
394
- "SELECT description FROM reminders WHERE status = 'PENDING' AND date <= date('now', '+1 day') ORDER BY date ASC LIMIT 5"
395
- ).fetchall()
396
- for r in reminders:
397
- query_parts.append(r['description'][:100])
398
-
399
- # 3. Last session diary topics
400
- try:
401
- last_diary = conn.execute(
402
- "SELECT summary FROM session_diary ORDER BY id DESC LIMIT 1"
403
- ).fetchone()
404
- if last_diary and last_diary['summary']:
405
- query_parts.append(last_diary['summary'][:200])
406
- except Exception:
407
- pass
408
-
409
- if not query_parts:
410
- return "No pending context to pre-load."
411
-
412
- # Search per-part to avoid diffuse centroid that matches everything
413
- try:
414
- import cognitive
415
- all_results = []
416
- seen_ids = set()
417
- for part in query_parts[:6]:
418
- part_results = cognitive.search(
419
- query_text=part,
420
- top_k=3,
421
- min_score=0.6,
422
- stores="both",
423
- rehearse=False, # Don't inflate strength on startup
424
- )
425
- for r in part_results:
426
- key = (r["store"], r["id"])
427
- if key not in seen_ids:
428
- seen_ids.add(key)
429
- all_results.append(r)
430
- # Sort by score descending, take top 10
431
- results = sorted(all_results, key=lambda x: x["score"], reverse=True)[:10]
432
- composite_query = " | ".join(query_parts[:6])
433
- if not results:
434
- return "Smart startup query: no relevant memories found."
435
-
436
- lines = [f"SMART STARTUP — {len(results)} memories pre-loaded from composite query:"]
437
- lines.append(f"Query: {composite_query[:200]}...")
438
- lines.append("")
439
- lines.append(cognitive.format_results(results))
440
-
441
- # Session tone from Deep Sleep (emotional intelligence layer)
442
- tone = _load_session_tone()
443
- if tone:
444
- lines.append("")
445
- lines.append(tone)
446
-
447
- return "\n".join(lines)
448
- except Exception as e:
449
- return f"Smart startup query error: {e}"
450
-
451
-
452
- def handle_stop(sid: str) -> str:
453
- """Cleanly close a session, removing it from active sessions immediately."""
454
- _stop_keepalive(sid)
455
- complete_session(sid)
456
- return f"Session {sid} closed."
457
-
458
-
459
- def handle_status(keyword: str | None = None) -> str:
460
- """List active sessions, optionally filtered by keyword."""
461
- clean_stale_sessions()
462
- if keyword:
463
- sessions = search_sessions(keyword)
464
- if not sessions:
465
- return f"Nobody is working on '{keyword}'."
466
- else:
467
- sessions = get_active_sessions()
468
-
469
- if not sessions:
470
- return "No active sessions."
471
-
472
- lines = ["ACTIVE SESSIONS:"]
473
- for s in sessions:
474
- age = _format_age(s["last_update_epoch"])
475
- lines.append(f" {s['sid']} ({age}) — {s['task']}")
476
- return "\n".join(lines)
@@ -1,57 +0,0 @@
1
- """Task history tools: log executions, list history, report overdue tasks."""
2
-
3
- import datetime
4
- from db import log_task, list_task_history, set_task_frequency, get_overdue_tasks, get_task_frequencies
5
-
6
-
7
- def _epoch_to_date(epoch: float) -> str:
8
- """Format epoch timestamp as readable date string."""
9
- return datetime.datetime.fromtimestamp(epoch).strftime("%Y-%m-%d %H:%M")
10
-
11
-
12
- def handle_task_log(task_num: str, task_name: str, notes: str = '', reasoning: str = '') -> str:
13
- """Record a task execution in the history log.
14
-
15
- Args:
16
- task_num: Task number identifier
17
- task_name: Task name
18
- notes: Execution notes
19
- reasoning: WHY this task was executed now — what triggered it, what data informed it
20
- """
21
- result = log_task(task_num, task_name, notes, reasoning)
22
- if "error" in result:
23
- return f"ERROR: {result['error']}"
24
- return f"Task {task_num} ({task_name}) logged."
25
-
26
-
27
- def handle_task_list(task_num: str = '', days: int = 30) -> str:
28
- """Show execution history for all tasks or a specific task number."""
29
- results = list_task_history(task_num if task_num else None, days)
30
- if not results:
31
- scope = f"task {task_num}" if task_num else "any task"
32
- return f"HISTORY: No executions of {scope} in recent days."
33
- lines = [f"HISTORY ({len(results)} executions, {days}d):"]
34
- for r in results:
35
- date_str = _epoch_to_date(r["executed_at"])
36
- notes_str = f": {r['notes']}" if r.get("notes") else ""
37
- lines.append(f" {date_str} — Task {r['task_num']} ({r['task_name']}){notes_str}")
38
- return "\n".join(lines)
39
-
40
-
41
- def handle_task_frequency() -> str:
42
- """Report tasks that are overdue based on their configured frequency."""
43
- overdue = get_overdue_tasks()
44
- if not overdue:
45
- return "All tasks up to date."
46
- lines = ["TAREAS VENCIDAS:"]
47
- for t in overdue:
48
- days_since = t.get("days_since_last")
49
- if days_since is not None:
50
- since_str = f"last run {days_since:.1f} days ago"
51
- else:
52
- since_str = "never executed"
53
- lines.append(
54
- f" Task {t['task_num']} ({t['task_name']}): "
55
- f"{since_str}, frequency every {t['frequency_days']} days"
56
- )
57
- return "\n".join(lines)
@@ -1,63 +0,0 @@
1
- <!-- nexo-claude-md-version: 1.0.0 -->
2
- # {{NAME}} — Cognitive Co-Operator
3
-
4
- I am {{NAME}}, a cognitive co-operator powered by NEXO Brain. Not an assistant — an operational partner.
5
- Tool-coupled behavioral rules (heartbeat, guard, trust, memory, diary) live in the MCP server instructions field and are injected automatically. This file is the bootstrap: identity, profile, format, and autonomy.
6
-
7
- <!-- nexo:start:startup -->
8
- ## Startup (4 steps)
9
- 1. `nexo_startup` → SID
10
- 2. In parallel: `nexo_session_diary_read(last_day=true)` + `nexo_reminders(filter="due")` + `nexo_reminders(filter="followups")` + read `{{NEXO_HOME}}/brain/calibration.json`
11
- 3. Execute overdue followups in background. Adopt `mental_state` from last diary (don't start cold).
12
- 4. Read user's name and language from `{{NEXO_HOME}}/brain/calibration.json` — ALWAYS communicate in that language.
13
-
14
- **Presentation:** {{NAME}} speaks first. Conversational greeting adapted to time of day. Tell what you HAVE DONE, not list pending items. Menu only if the user asks.
15
- <!-- nexo:end:startup -->
16
-
17
- <!-- nexo:start:profile -->
18
- ## User Profile
19
- - **Calibration:** `{{NEXO_HOME}}/brain/calibration.json` (personality settings + language + user name)
20
- - **Profile:** `{{NEXO_HOME}}/brain/profile.json` (deep scan results from onboarding)
21
-
22
- ### First Session Onboarding (only if profile.json lacks `role` or `technical_level`)
23
- Ask TWO questions: (1) "What do you do?" → save to profile.json + `nexo_preference_set("role", answer)`. (2) "Technical level? Beginner / Intermediate / Advanced" → save + `nexo_preference_set("technical_level", answer)`. Then: "Got it. From now on I learn by observing." Never ask onboarding questions again.
24
-
25
- ### Personality Calibration (from calibration.json)
26
- - **autonomy:** conservative (ask first) | balanced (default) | full (act first)
27
- - **communication:** concise | balanced (default) | detailed
28
- - **honesty:** firm-pushback (default) | mention-and-follow | just-execute
29
- - **proactivity:** reactive | suggestive (default) | proactive
30
- - **error_handling:** brief-fix (default) | explain-and-learn
31
- <!-- nexo:end:profile -->
32
-
33
- <!-- nexo:start:autonomy -->
34
- ## Autonomy
35
- Install tools, create scripts, run commands — whatever it takes. NEVER push manual steps to the user. NEVER say "I can't".
36
-
37
- | If you were about to say... | Do this instead |
38
- |---|---|
39
- | "I don't have X installed" | `brew/pip/npm install X` |
40
- | "You need to go to the website..." | curl, API, MCP, or script |
41
- | "Open the browser..." | Browser MCP or curl |
42
- | "I don't have access..." | `nexo_credential_get`, SSH, find alternative |
43
- <!-- nexo:end:autonomy -->
44
-
45
- <!-- nexo:start:atlas -->
46
- ## Project Atlas
47
- `{{NEXO_HOME}}/brain/project-atlas.json` — search BEFORE touching any project. Never assume server, port, or code location.
48
-
49
- Credentials: (1) `nexo_credential_get`, (2) `{{NEXO_HOME}}/*.txt/*.json`.
50
- <!-- nexo:end:atlas -->
51
-
52
- <!-- nexo:start:hooks -->
53
- ## Hooks
54
- - **SessionStart** → generates briefing at `{{NEXO_HOME}}/coordination/session-briefing.txt`. Read during startup.
55
- - **Stop** → injects post-mortem instructions. Write diary, buffer entry, followups, proactive seeds.
56
- - **PostToolUse** → logs mutations to `session_buffer.jsonl` silently.
57
- - **PreCompact** → saves checkpoint. Write diary IMMEDIATELY when you see this, then read checkpoint after compaction.
58
- <!-- nexo:end:hooks -->
59
-
60
- <!-- nexo:start:menu -->
61
- ## Menu
62
- **On demand only.** `nexo_menu` when user asks. NEVER show at startup.
63
- <!-- nexo:end:menu -->
@@ -1,13 +0,0 @@
1
- {
2
- "mcp": {
3
- "servers": {
4
- "nexo-brain": {
5
- "command": "python3",
6
- "args": ["~/.nexo/server.py"],
7
- "env": {
8
- "NEXO_HOME": "~/.nexo"
9
- }
10
- }
11
- }
12
- }
13
- }
File without changes
package/tests/__init__.py DELETED
File without changes
@@ -1,71 +0,0 @@
1
- """Shared fixtures for NEXO test suite.
2
-
3
- Uses isolated temp databases so tests never touch production data.
4
- """
5
-
6
- import os
7
- import sys
8
- import sqlite3
9
-
10
- import pytest
11
-
12
- # Add src/ to path so we can import nexo modules
13
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
14
-
15
-
16
- @pytest.fixture(autouse=True)
17
- def isolated_db(tmp_path, monkeypatch):
18
- """Redirect both nexo.db and cognitive.db to temp files per test."""
19
- test_db = str(tmp_path / "test_nexo.db")
20
- test_cog_db = str(tmp_path / "test_cognitive.db")
21
-
22
- monkeypatch.setenv("NEXO_TEST_DB", test_db)
23
- monkeypatch.setenv("NEXO_COGNITIVE_DB", test_cog_db)
24
- monkeypatch.setenv("NEXO_SKIP_FS_INDEX", "1")
25
-
26
- import db._core as db_core
27
- import cognitive._core as cog_core
28
-
29
- # Close existing connections
30
- db_core.close_db()
31
- if cog_core._conn is not None:
32
- try:
33
- cog_core._conn.close()
34
- except Exception:
35
- pass
36
- cog_core._conn = None
37
-
38
- # Point to temp paths
39
- db_core.DB_PATH = test_db
40
- cog_core.COGNITIVE_DB = test_cog_db
41
-
42
- # Create a fresh raw connection
43
- raw = sqlite3.connect(test_db, timeout=30, check_same_thread=False,
44
- isolation_level=None)
45
- raw.execute("PRAGMA journal_mode=WAL")
46
- raw.execute("PRAGMA busy_timeout=30000")
47
- raw.execute("PRAGMA foreign_keys=ON")
48
- raw.row_factory = sqlite3.Row
49
-
50
- wrapped = db_core._SerializedConnection(raw)
51
- db_core._shared_conn = wrapped
52
-
53
- # Initialize schemas
54
- from db._core import init_db
55
- from db._schema import run_migrations
56
- init_db()
57
- run_migrations()
58
-
59
- yield {
60
- "nexo_db": test_db,
61
- "cognitive_db": test_cog_db,
62
- }
63
-
64
- # Cleanup
65
- db_core.close_db()
66
- if cog_core._conn is not None:
67
- try:
68
- cog_core._conn.close()
69
- except Exception:
70
- pass
71
- cog_core._conn = None