nexo-brain 5.3.26 → 5.3.27

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 (211) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/server.py +3 -0
  4. package/src/tools_sessions.py +6 -1
  5. package/src/dashboard/static/favicon 2.svg +0 -32
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  8. package/src/dashboard/static/style 2.css +0 -2458
  9. package/src/dashboard/templates/adaptive 2.html +0 -118
  10. package/src/dashboard/templates/artifacts 2.html +0 -133
  11. package/src/dashboard/templates/backups 2.html +0 -136
  12. package/src/dashboard/templates/base 2.html +0 -417
  13. package/src/dashboard/templates/calendar 2.html +0 -591
  14. package/src/dashboard/templates/chat 2.html +0 -356
  15. package/src/dashboard/templates/claims 2.html +0 -259
  16. package/src/dashboard/templates/cortex 2.html +0 -321
  17. package/src/dashboard/templates/credentials 2.html +0 -128
  18. package/src/dashboard/templates/crons 2.html +0 -370
  19. package/src/dashboard/templates/dashboard 2.html +0 -494
  20. package/src/dashboard/templates/dreams 2.html +0 -252
  21. package/src/dashboard/templates/email 2.html +0 -160
  22. package/src/dashboard/templates/evolution 2.html +0 -189
  23. package/src/dashboard/templates/feed 2.html +0 -249
  24. package/src/dashboard/templates/followup_health 2.html +0 -170
  25. package/src/dashboard/templates/graph 2.html +0 -201
  26. package/src/dashboard/templates/guard 2.html +0 -259
  27. package/src/dashboard/templates/inbox 2.html +0 -251
  28. package/src/dashboard/templates/memory 2.html +0 -420
  29. package/src/dashboard/templates/operations 2.html +0 -608
  30. package/src/dashboard/templates/plugins 2.html +0 -185
  31. package/src/dashboard/templates/protocol 2.html +0 -199
  32. package/src/dashboard/templates/rules 2.html +0 -246
  33. package/src/dashboard/templates/sentiment 2.html +0 -247
  34. package/src/dashboard/templates/sessions 2.html +0 -218
  35. package/src/dashboard/templates/skills 2.html +0 -329
  36. package/src/dashboard/templates/somatic 2.html +0 -73
  37. package/src/dashboard/templates/triggers 2.html +0 -133
  38. package/src/dashboard/templates/trust 2.html +0 -360
  39. package/src/db/__init__ 2.py +0 -259
  40. package/src/db/_core 2.py +0 -437
  41. package/src/db/_credentials 2.py +0 -124
  42. package/src/db/_episodic 2.py +0 -762
  43. package/src/db/_evolution 2.py +0 -54
  44. package/src/db/_fts 2.py +0 -406
  45. package/src/db/_goal_profiles 2.py +0 -376
  46. package/src/db/_hot_context 2.py +0 -660
  47. package/src/db/_outcomes 2.py +0 -800
  48. package/src/db/_personal_scripts 2.py +0 -582
  49. package/src/db/_sessions 2.py +0 -330
  50. package/src/db/_tasks 2.py +0 -91
  51. package/src/db/_watchers 2.py +0 -173
  52. package/src/doctor/formatters 2.py +0 -52
  53. package/src/doctor/models 2.py +0 -69
  54. package/src/doctor/planes 2.py +0 -87
  55. package/src/doctor/providers/__init__ 2.py +0 -1
  56. package/src/doctor/providers/deep 2.py +0 -367
  57. package/src/evolution_cycle 2.py +0 -519
  58. package/src/hooks/auto_capture 2.py +0 -208
  59. package/src/hooks/caffeinate-guard 2.sh +0 -8
  60. package/src/hooks/capture-session 2.sh +0 -21
  61. package/src/hooks/capture-tool-logs 2.sh +0 -158
  62. package/src/hooks/daily-briefing-check 2.sh +0 -33
  63. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  64. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  65. package/src/hooks/inbox-hook 2.sh +0 -76
  66. package/src/hooks/post-compact 2.sh +0 -152
  67. package/src/hooks/pre-compact 2.sh +0 -169
  68. package/src/hooks/protocol-guardrail 2.sh +0 -10
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  70. package/src/hooks/session-stop 2.sh +0 -52
  71. package/src/kg_populate 2.py +0 -292
  72. package/src/maintenance 2.py +0 -53
  73. package/src/memory_backends 2.py +0 -71
  74. package/src/migrate_embeddings 2.py +0 -124
  75. package/src/nexo_sdk 2.py +0 -103
  76. package/src/observability 2.py +0 -199
  77. package/src/plugin_loader 2.py +0 -217
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +0 -450
  80. package/src/plugins/backup 2.py +0 -127
  81. package/src/plugins/claims_tools 2.py +0 -119
  82. package/src/plugins/cognitive_memory 2.py +0 -609
  83. package/src/plugins/core_rules 2.py +0 -252
  84. package/src/plugins/cortex 2.py +0 -1155
  85. package/src/plugins/entities 2.py +0 -67
  86. package/src/plugins/episodic_memory 2.py +0 -560
  87. package/src/plugins/evolution 2.py +0 -167
  88. package/src/plugins/goal_engine 2.py +0 -142
  89. package/src/plugins/guard 2.py +0 -862
  90. package/src/plugins/impact 2.py +0 -29
  91. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  92. package/src/plugins/media_memory_tools 2.py +0 -98
  93. package/src/plugins/memory_export 2.py +0 -196
  94. package/src/plugins/outcomes 2.py +0 -130
  95. package/src/plugins/personal_scripts 2.py +0 -117
  96. package/src/plugins/preferences 2.py +0 -47
  97. package/src/plugins/protocol 2.py +0 -1449
  98. package/src/plugins/simple_api 2.py +0 -106
  99. package/src/plugins/skills 2.py +0 -341
  100. package/src/plugins/state_watchers 2.py +0 -79
  101. package/src/plugins/update 2.py +0 -986
  102. package/src/plugins/user_state_tools 2.py +0 -43
  103. package/src/plugins/workflow 2.py +0 -588
  104. package/src/protocol_settings 2.py +0 -59
  105. package/src/public_contribution 2.py +0 -466
  106. package/src/public_evolution_queue 2.py +0 -241
  107. package/src/requirements 2.txt +0 -14
  108. package/src/retroactive_learnings 2.py +0 -373
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +0 -331
  111. package/src/rules/migrate 2.py +0 -207
  112. package/src/runtime_power 2.py +0 -874
  113. package/src/script_registry 2.py +0 -1559
  114. package/src/scripts/check-context 2.py +0 -272
  115. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  116. package/src/scripts/deep-sleep/collect 2.py +0 -928
  117. package/src/scripts/deep-sleep/extract 2.py +0 -330
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  119. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  121. package/src/scripts/nexo-agent-run 2.py +0 -75
  122. package/src/scripts/nexo-auto-update 2.py +0 -6
  123. package/src/scripts/nexo-backup 2.sh +0 -25
  124. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  125. package/src/scripts/nexo-catchup 2.py +0 -300
  126. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  127. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  128. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  129. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  130. package/src/scripts/nexo-dashboard 2.sh +0 -29
  131. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  132. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  133. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  134. package/src/scripts/nexo-hook-record 2.py +0 -42
  135. package/src/scripts/nexo-immune 2.py +0 -936
  136. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  137. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  138. package/src/scripts/nexo-install 2.py +0 -6
  139. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  140. package/src/scripts/nexo-learning-validator 2.py +0 -266
  141. package/src/scripts/nexo-migrate 2.py +0 -260
  142. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  144. package/src/scripts/nexo-pre-commit 2.py +0 -120
  145. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  146. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  147. package/src/scripts/nexo-reflection 2.py +0 -256
  148. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  149. package/src/scripts/nexo-sleep 2.py +0 -631
  150. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  151. package/src/scripts/nexo-sync-clients 2.py +0 -16
  152. package/src/scripts/nexo-synthesis 2.py +0 -475
  153. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  154. package/src/scripts/nexo-update 2.sh +0 -306
  155. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  156. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  158. package/src/server 2.py +0 -1296
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  164. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  165. package/src/skills/run-release-final-audit/script 2.py +0 -259
  166. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  167. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  168. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  169. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  170. package/src/skills_runtime 2.py +0 -932
  171. package/src/state_watchers_runtime 2.py +0 -475
  172. package/src/storage_router 2.py +0 -32
  173. package/src/system_catalog 2.py +0 -786
  174. package/src/tools_coordination 2.py +0 -103
  175. package/src/tools_credentials 2.py +0 -68
  176. package/src/tools_drive 2.py +0 -487
  177. package/src/tools_hot_context 2.py +0 -163
  178. package/src/tools_learnings 2.py +0 -612
  179. package/src/tools_menu 2.py +0 -229
  180. package/src/tools_reminders 2.py +0 -88
  181. package/src/tools_reminders_crud 2.py +0 -363
  182. package/src/tools_sessions 2.py +0 -1054
  183. package/src/tools_system_catalog 2.py +0 -19
  184. package/src/tools_task_history 2.py +0 -57
  185. package/src/tools_transcripts 2.py +0 -98
  186. package/src/transcript_utils 2.py +0 -412
  187. package/src/user_context 2.py +0 -46
  188. package/src/user_data_portability 2.py +0 -328
  189. package/src/user_state_model 2.py +0 -170
  190. package/templates/CLAUDE.md 2.template +0 -108
  191. package/templates/CODEX.AGENTS.md 2.template +0 -66
  192. package/templates/launchagents/README 2.md +0 -132
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  194. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  200. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  205. package/templates/nexo_helper 2.py +0 -301
  206. package/templates/openclaw 2.json +0 -13
  207. package/templates/plugin-template 2.py +0 -40
  208. package/templates/script-template 2.py +0 -59
  209. package/templates/script-template 2.sh +0 -13
  210. package/templates/skill-script-template 2.py +0 -48
  211. package/templates/skill-template 2.md +0 -33
@@ -1,437 +0,0 @@
1
- from __future__ import annotations
2
- """SQLite database for NEXO session coordination."""
3
-
4
- import sqlite3
5
- import time
6
- import os
7
- import secrets
8
- import string
9
- import datetime
10
- import pathlib
11
- import threading
12
-
13
- NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
14
- _data_dir = os.path.join(NEXO_HOME, "data")
15
- os.makedirs(_data_dir, exist_ok=True)
16
-
17
- DB_PATH = os.environ.get(
18
- "NEXO_TEST_DB",
19
- os.environ.get(
20
- "NEXO_DB",
21
- os.path.join(_data_dir, "nexo.db"),
22
- ),
23
- )
24
-
25
- # TTLs in seconds (match session-coord.sh behavior)
26
- SESSION_STALE_SECONDS = 900 # 15 min (documented TTL)
27
- MESSAGE_TTL_SECONDS = 3600 # 1 hour
28
- QUESTION_TTL_SECONDS = 600 # 10 min
29
-
30
- # Single shared connection per process with write serialization.
31
- # SQLite allows only one writer at a time. Using a shared connection with
32
- # check_same_thread=False and a write lock ensures:
33
- # - No FTS5 corruption from concurrent write connections
34
- # - Reads can happen freely (WAL allows concurrent readers)
35
- # - Writes are serialized via _write_lock to prevent 'database is locked' errors
36
- _shared_conn: sqlite3.Connection | None = None
37
- _write_lock = threading.RLock() # RLock allows re-entrant locking (function A calls B, both serialize)
38
-
39
-
40
- def get_db() -> sqlite3.Connection:
41
- """Get shared database connection with WAL mode.
42
-
43
- Returns a _SerializedConnection wrapper that serializes all execute
44
- calls via _write_lock, preventing race conditions and FTS5 corruption
45
- under concurrent thread access.
46
- """
47
- global _shared_conn
48
- if _shared_conn is None:
49
- raw = sqlite3.connect(
50
- DB_PATH, timeout=30, check_same_thread=False,
51
- isolation_level=None, # autocommit — no implicit BEGIN holding locks
52
- )
53
- raw.execute("PRAGMA journal_mode=WAL")
54
- raw.execute("PRAGMA busy_timeout=30000")
55
- raw.execute("PRAGMA foreign_keys=ON")
56
- raw.execute("PRAGMA wal_autocheckpoint=1000")
57
- raw.row_factory = sqlite3.Row
58
- _shared_conn = _SerializedConnection(raw)
59
- return _shared_conn
60
-
61
-
62
- def close_db():
63
- """Close the shared database connection. Called on shutdown signals."""
64
- global _shared_conn
65
- if _shared_conn is not None:
66
- try:
67
- _shared_conn.close()
68
- except Exception:
69
- pass
70
- _shared_conn = None
71
-
72
-
73
- def _get_raw_conn() -> sqlite3.Connection:
74
- """Get the raw unwrapped connection (for PRAGMA queries that need direct access)."""
75
- conn = get_db()
76
- if isinstance(conn, _SerializedConnection):
77
- return conn._conn
78
- return conn
79
-
80
-
81
- class _SerializedConnection:
82
- """Wrapper around sqlite3.Connection that serializes all execute calls.
83
-
84
- SQLite with a single shared connection and check_same_thread=False needs
85
- serialization to prevent:
86
- - Stale lastrowid when concurrent INSERTs happen
87
- - FTS5 index corruption from concurrent writes
88
- - 'NoneType' errors from interleaved INSERT+SELECT sequences
89
-
90
- All execute/executemany/executescript calls go through _write_lock.
91
- Property access (row_factory etc.) passes through directly.
92
- """
93
- def __init__(self, conn: sqlite3.Connection):
94
- self._conn = conn
95
-
96
- def execute(self, *args, **kwargs):
97
- with _write_lock:
98
- return self._conn.execute(*args, **kwargs)
99
-
100
- def executemany(self, *args, **kwargs):
101
- with _write_lock:
102
- return self._conn.executemany(*args, **kwargs)
103
-
104
- def executescript(self, *args, **kwargs):
105
- with _write_lock:
106
- return self._conn.executescript(*args, **kwargs)
107
-
108
- def commit(self):
109
- with _write_lock:
110
- return self._conn.commit()
111
-
112
- def close(self):
113
- return self._conn.close()
114
-
115
- def __getattr__(self, name):
116
- return getattr(self._conn, name)
117
-
118
- def __setattr__(self, name, value):
119
- if name == '_conn':
120
- super().__setattr__(name, value)
121
- else:
122
- setattr(self._conn, name, value)
123
-
124
-
125
- def init_db():
126
- """Create tables if they don't exist."""
127
- conn = get_db()
128
- conn.executescript("""
129
- CREATE TABLE IF NOT EXISTS sessions (
130
- sid TEXT PRIMARY KEY,
131
- task TEXT NOT NULL DEFAULT '',
132
- started_epoch REAL NOT NULL,
133
- last_update_epoch REAL NOT NULL,
134
- local_time TEXT NOT NULL DEFAULT ''
135
- );
136
-
137
- CREATE TABLE IF NOT EXISTS tracked_files (
138
- sid TEXT NOT NULL,
139
- path TEXT NOT NULL,
140
- tracked_at REAL NOT NULL,
141
- PRIMARY KEY (sid, path),
142
- FOREIGN KEY (sid) REFERENCES sessions(sid) ON DELETE CASCADE
143
- );
144
-
145
- CREATE TABLE IF NOT EXISTS messages (
146
- id TEXT PRIMARY KEY,
147
- from_sid TEXT NOT NULL,
148
- to_sid TEXT NOT NULL,
149
- text TEXT NOT NULL,
150
- created_epoch REAL NOT NULL
151
- );
152
-
153
- CREATE TABLE IF NOT EXISTS message_reads (
154
- message_id TEXT NOT NULL,
155
- sid TEXT NOT NULL,
156
- PRIMARY KEY (message_id, sid),
157
- FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
158
- );
159
-
160
- CREATE TABLE IF NOT EXISTS questions (
161
- qid TEXT PRIMARY KEY,
162
- from_sid TEXT NOT NULL,
163
- to_sid TEXT NOT NULL,
164
- question TEXT NOT NULL,
165
- answer TEXT,
166
- status TEXT NOT NULL DEFAULT 'pending',
167
- created_epoch REAL NOT NULL,
168
- answered_epoch REAL
169
- );
170
-
171
-
172
- CREATE TABLE IF NOT EXISTS reminders (
173
- id TEXT PRIMARY KEY,
174
- date TEXT,
175
- description TEXT NOT NULL,
176
- status TEXT NOT NULL DEFAULT 'PENDING',
177
- category TEXT DEFAULT 'general',
178
- created_at REAL NOT NULL,
179
- updated_at REAL NOT NULL
180
- );
181
-
182
- CREATE TABLE IF NOT EXISTS followups (
183
- id TEXT PRIMARY KEY,
184
- date TEXT,
185
- description TEXT NOT NULL,
186
- verification TEXT DEFAULT '',
187
- status TEXT NOT NULL DEFAULT 'PENDING',
188
- recurrence TEXT DEFAULT NULL,
189
- created_at REAL NOT NULL,
190
- updated_at REAL NOT NULL
191
- );
192
-
193
- CREATE TABLE IF NOT EXISTS item_history (
194
- id INTEGER PRIMARY KEY AUTOINCREMENT,
195
- item_type TEXT NOT NULL,
196
- item_id TEXT NOT NULL,
197
- event_type TEXT NOT NULL,
198
- note TEXT DEFAULT '',
199
- actor TEXT DEFAULT '',
200
- metadata TEXT DEFAULT '{}',
201
- created_at REAL NOT NULL
202
- );
203
-
204
- CREATE TABLE IF NOT EXISTS item_read_tokens (
205
- token TEXT PRIMARY KEY,
206
- item_type TEXT NOT NULL,
207
- item_id TEXT NOT NULL,
208
- history_seq INTEGER DEFAULT 0,
209
- issued_at REAL NOT NULL,
210
- expires_at REAL NOT NULL
211
- );
212
-
213
- CREATE TABLE IF NOT EXISTS learnings (
214
- id INTEGER PRIMARY KEY AUTOINCREMENT,
215
- category TEXT NOT NULL,
216
- title TEXT NOT NULL,
217
- content TEXT NOT NULL,
218
- created_at REAL NOT NULL,
219
- updated_at REAL NOT NULL
220
- );
221
-
222
- CREATE TABLE IF NOT EXISTS credentials (
223
- id INTEGER PRIMARY KEY AUTOINCREMENT,
224
- service TEXT NOT NULL,
225
- key TEXT NOT NULL,
226
- value TEXT NOT NULL,
227
- notes TEXT DEFAULT '',
228
- created_at REAL NOT NULL,
229
- updated_at REAL NOT NULL,
230
- UNIQUE(service, key)
231
- );
232
-
233
- CREATE TABLE IF NOT EXISTS task_history (
234
- id INTEGER PRIMARY KEY AUTOINCREMENT,
235
- task_num TEXT NOT NULL,
236
- task_name TEXT NOT NULL,
237
- executed_at REAL NOT NULL,
238
- notes TEXT DEFAULT ''
239
- );
240
-
241
- CREATE TABLE IF NOT EXISTS task_frequencies (
242
- task_num TEXT PRIMARY KEY,
243
- task_name TEXT NOT NULL,
244
- frequency_days INTEGER NOT NULL,
245
- description TEXT DEFAULT ''
246
- );
247
-
248
- CREATE TABLE IF NOT EXISTS plugins (
249
- filename TEXT PRIMARY KEY,
250
- tools_count INTEGER DEFAULT 0,
251
- tool_names TEXT DEFAULT '',
252
- loaded_at REAL,
253
- created_by TEXT DEFAULT 'manual'
254
- );
255
-
256
- CREATE TABLE IF NOT EXISTS entities (
257
- id INTEGER PRIMARY KEY AUTOINCREMENT,
258
- name TEXT NOT NULL,
259
- type TEXT NOT NULL DEFAULT 'general',
260
- value TEXT NOT NULL,
261
- notes TEXT DEFAULT '',
262
- created_at REAL NOT NULL,
263
- updated_at REAL NOT NULL
264
- );
265
-
266
- CREATE TABLE IF NOT EXISTS preferences (
267
- key TEXT PRIMARY KEY,
268
- value TEXT NOT NULL,
269
- category TEXT DEFAULT 'general',
270
- updated_at REAL NOT NULL
271
- );
272
-
273
- CREATE TABLE IF NOT EXISTS agents (
274
- id TEXT PRIMARY KEY,
275
- name TEXT NOT NULL,
276
- specialization TEXT NOT NULL,
277
- model TEXT DEFAULT 'sonnet',
278
- tools TEXT DEFAULT '',
279
- context_files TEXT DEFAULT '',
280
- rules TEXT DEFAULT '',
281
- created_at REAL NOT NULL,
282
- updated_at REAL NOT NULL
283
- );
284
-
285
- CREATE TABLE IF NOT EXISTS change_log (
286
- id INTEGER PRIMARY KEY AUTOINCREMENT,
287
- session_id TEXT NOT NULL,
288
- created_at TEXT DEFAULT (datetime('now')),
289
- files TEXT NOT NULL,
290
- what_changed TEXT NOT NULL,
291
- why TEXT NOT NULL,
292
- triggered_by TEXT DEFAULT '',
293
- affects TEXT DEFAULT '',
294
- risks TEXT DEFAULT '',
295
- verify TEXT DEFAULT '',
296
- commit_ref TEXT DEFAULT ''
297
- );
298
-
299
- CREATE TABLE IF NOT EXISTS decisions (
300
- id INTEGER PRIMARY KEY AUTOINCREMENT,
301
- session_id TEXT NOT NULL,
302
- created_at TEXT DEFAULT (datetime('now')),
303
- domain TEXT NOT NULL,
304
- decision TEXT NOT NULL,
305
- alternatives TEXT,
306
- based_on TEXT,
307
- confidence TEXT DEFAULT 'medium',
308
- context_ref TEXT,
309
- outcome TEXT,
310
- outcome_at TEXT
311
- );
312
-
313
- CREATE TABLE IF NOT EXISTS session_diary (
314
- id INTEGER PRIMARY KEY AUTOINCREMENT,
315
- session_id TEXT NOT NULL,
316
- created_at TEXT DEFAULT (datetime('now')),
317
- decisions TEXT NOT NULL,
318
- discarded TEXT,
319
- pending TEXT,
320
- context_next TEXT,
321
- mental_state TEXT,
322
- domain TEXT,
323
- user_signals TEXT,
324
- summary TEXT NOT NULL
325
- );
326
-
327
- CREATE TABLE IF NOT EXISTS evolution_metrics (
328
- id INTEGER PRIMARY KEY AUTOINCREMENT,
329
- dimension TEXT NOT NULL,
330
- score INTEGER NOT NULL CHECK(score >= 0 AND score <= 100),
331
- measured_at TEXT DEFAULT (datetime('now')),
332
- evidence TEXT NOT NULL,
333
- delta INTEGER DEFAULT 0
334
- );
335
-
336
- CREATE TABLE IF NOT EXISTS evolution_log (
337
- id INTEGER PRIMARY KEY AUTOINCREMENT,
338
- created_at TEXT DEFAULT (datetime('now')),
339
- cycle_number INTEGER NOT NULL,
340
- dimension TEXT NOT NULL,
341
- proposal TEXT NOT NULL,
342
- classification TEXT NOT NULL DEFAULT 'auto',
343
- status TEXT DEFAULT 'pending',
344
- files_changed TEXT,
345
- snapshot_ref TEXT,
346
- test_result TEXT,
347
- impact INTEGER DEFAULT 0,
348
- reasoning TEXT NOT NULL
349
- );
350
- """)
351
- # foreign_keys=ON is set in get_db() per-connection
352
-
353
- # ── Run formal migrations ────────────────────────────────────
354
- from db._schema import run_migrations
355
- run_migrations(conn)
356
-
357
- # ── FTS5 unified search index ────────────────────────────────
358
- conn.execute("""
359
- CREATE VIRTUAL TABLE IF NOT EXISTS unified_search USING fts5(
360
- source,
361
- source_id,
362
- title,
363
- body,
364
- category,
365
- updated_at UNINDEXED,
366
- tokenize='unicode61 remove_diacritics 2'
367
- )
368
- """)
369
-
370
- # Dynamic directory registry for FTS indexing
371
- conn.execute("""
372
- CREATE TABLE IF NOT EXISTS fts_dirs (
373
- id INTEGER PRIMARY KEY AUTOINCREMENT,
374
- path TEXT NOT NULL UNIQUE,
375
- dir_type TEXT NOT NULL DEFAULT 'code',
376
- patterns TEXT NOT NULL DEFAULT '*.php,*.js,*.json,*.py,*.ts,*.tsx',
377
- added_at REAL NOT NULL,
378
- notes TEXT DEFAULT ''
379
- )
380
- """)
381
- conn.commit()
382
-
383
- if os.environ.get("NEXO_SKIP_FS_INDEX", "0") != "1":
384
- # FTS refresh in background thread — never block server startup
385
- import threading
386
-
387
- def _bg_fts():
388
- try:
389
- bg_conn = sqlite3.connect(DB_PATH, timeout=30)
390
- bg_conn.execute("PRAGMA journal_mode=WAL")
391
- bg_conn.execute("PRAGMA busy_timeout=30000")
392
- bg_conn.row_factory = sqlite3.Row
393
- row = bg_conn.execute("SELECT COUNT(*) FROM unified_search").fetchone()
394
- from db._fts import rebuild_fts_index, _refresh_fts_files
395
- if row[0] == 0:
396
- rebuild_fts_index(bg_conn)
397
- else:
398
- _refresh_fts_files(bg_conn)
399
- bg_conn.close()
400
- except Exception:
401
- pass
402
-
403
- threading.Thread(target=_bg_fts, daemon=True).start()
404
-
405
-
406
-
407
- def _gen_id(prefix: str, length: int = 8) -> str:
408
- """Generate a random ID like 'msg-a1b2c3' or 'q-x9y8z7w6'."""
409
- chars = string.ascii_lowercase + string.digits
410
- suffix = ''.join(secrets.choice(chars) for _ in range(length))
411
- return f"{prefix}-{suffix}"
412
-
413
-
414
- # ── Session operations ──────────────────────────────────────────────
415
-
416
- def now_epoch() -> float:
417
- return time.time()
418
-
419
-
420
- def local_time_str() -> str:
421
- from datetime import datetime
422
- return datetime.now().strftime("%H:%M")
423
-
424
-
425
- def _multi_word_like(query: str, columns: list[str]) -> tuple[str, list]:
426
- """Build AND-ed LIKE conditions: every word must appear in at least one column."""
427
- words = query.strip().split()
428
- if not words:
429
- return "1=1", []
430
- word_conditions = []
431
- params = []
432
- for word in words:
433
- pattern = f"%{word}%"
434
- col_or = " OR ".join(f"{c} LIKE ?" for c in columns)
435
- word_conditions.append(f"({col_or})")
436
- params.extend([pattern] * len(columns))
437
- return " AND ".join(word_conditions), params
@@ -1,124 +0,0 @@
1
- """NEXO DB — Credentials module."""
2
- import sqlite3, time
3
- from db._core import get_db, now_epoch
4
-
5
- # ── Credentials ────────────────────────────────────────────────────
6
-
7
- def create_credential(service: str, key: str, value: str, notes: str = '') -> dict:
8
- """Create a new credential entry."""
9
- conn = get_db()
10
- now = now_epoch()
11
- try:
12
- conn.execute(
13
- "INSERT INTO credentials (service, key, value, notes, created_at, updated_at) "
14
- "VALUES (?, ?, ?, ?, ?, ?)",
15
- (service, key, value, notes, now, now)
16
- )
17
- conn.commit()
18
- except sqlite3.IntegrityError:
19
- return {"error": f"Credential {service}/{key} already exists. Use update instead."}
20
- row = conn.execute(
21
- "SELECT * FROM credentials WHERE service = ? AND key = ?", (service, key)
22
- ).fetchone()
23
- return dict(row)
24
-
25
-
26
- def update_credential(service: str, key: str, value: str = None, notes: str = None) -> dict:
27
- """Update value and/or notes for a credential."""
28
- conn = get_db()
29
- row = conn.execute(
30
- "SELECT * FROM credentials WHERE service = ? AND key = ?", (service, key)
31
- ).fetchone()
32
- if not row:
33
- return {"error": f"Credential {service}/{key} not found"}
34
- updates = {"updated_at": now_epoch()}
35
- if value is not None:
36
- updates["value"] = value
37
- if notes is not None:
38
- updates["notes"] = notes
39
- set_clause = ", ".join(f"{k} = ?" for k in updates)
40
- values = list(updates.values()) + [service, key]
41
- conn.execute(
42
- f"UPDATE credentials SET {set_clause} WHERE service = ? AND key = ?", values
43
- )
44
- conn.commit()
45
- row = conn.execute(
46
- "SELECT * FROM credentials WHERE service = ? AND key = ?", (service, key)
47
- ).fetchone()
48
- return dict(row)
49
-
50
-
51
- def delete_credential(service: str, key: str = None) -> bool:
52
- """Delete credential(s). If key=None, delete all for the service."""
53
- conn = get_db()
54
- if key:
55
- result = conn.execute(
56
- "DELETE FROM credentials WHERE service = ? AND key = ?", (service, key)
57
- )
58
- else:
59
- result = conn.execute(
60
- "DELETE FROM credentials WHERE service = ?", (service,)
61
- )
62
- conn.commit()
63
- deleted = result.rowcount > 0
64
- return deleted
65
-
66
-
67
- def get_credential(service: str, key: str = None) -> list[dict]:
68
- """Get credential(s). If key=None, return all for the service.
69
-
70
- When exact match fails, performs fuzzy search across service, key,
71
- value and notes fields. Returns results tagged with _fuzzy=True so
72
- the caller can differentiate suggestions from exact hits.
73
- """
74
- conn = get_db()
75
- if key:
76
- rows = conn.execute(
77
- "SELECT * FROM credentials WHERE service = ? AND key = ?", (service, key)
78
- ).fetchall()
79
- else:
80
- rows = conn.execute(
81
- "SELECT * FROM credentials WHERE service = ?", (service,)
82
- ).fetchall()
83
- if rows:
84
- return [dict(r) for r in rows]
85
-
86
- # Fuzzy fallback: search term in service, key and notes (not value — too noisy)
87
- # Prioritize: service/key matches first, notes-only matches second
88
- term = f"%{service}%"
89
- fuzzy_rows = conn.execute(
90
- "SELECT *, "
91
- "CASE WHEN service LIKE ? THEN 0 "
92
- " WHEN key LIKE ? THEN 1 "
93
- " ELSE 2 END AS _rank "
94
- "FROM credentials WHERE "
95
- "service LIKE ? OR key LIKE ? OR notes LIKE ? "
96
- "ORDER BY _rank ASC, service ASC, key ASC",
97
- (term, term, term, term, term),
98
- ).fetchall()
99
- results = []
100
- for r in fuzzy_rows:
101
- d = dict(r)
102
- d["_fuzzy"] = True
103
- d.pop("_rank", None)
104
- results.append(d)
105
- return results
106
-
107
-
108
- def list_credentials(service: str = None) -> list[dict]:
109
- """List service+key only (NO values) for security."""
110
- conn = get_db()
111
- if service:
112
- rows = conn.execute(
113
- "SELECT id, service, key, notes, created_at, updated_at "
114
- "FROM credentials WHERE service = ? ORDER BY key ASC",
115
- (service,)
116
- ).fetchall()
117
- else:
118
- rows = conn.execute(
119
- "SELECT id, service, key, notes, created_at, updated_at "
120
- "FROM credentials ORDER BY service ASC, key ASC"
121
- ).fetchall()
122
- return [dict(r) for r in rows]
123
-
124
-