nexo-brain 1.7.0 → 2.1.0
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.
- package/README.md +160 -60
- package/bin/nexo-brain.js +680 -381
- package/package.json +18 -3
- package/scripts/migrate-to-unified.sh +813 -0
- package/scripts/migrate-v1.7-to-v1.8.py +214 -0
- package/scripts/pre-commit-check.sh +1 -1
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions.py +1 -1
- package/src/auto_update.py +634 -0
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core.py +7 -3
- package/src/cognitive/_decay.py +1 -1
- package/src/cognitive/_search.py +1 -0
- package/src/cognitive/_trust.py +3 -3
- package/src/crons/manifest.json +106 -0
- package/src/crons/sync.py +217 -0
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app.py +24 -4
- package/src/dashboard/templates/dashboard.html +3 -2
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core.py +5 -1
- package/src/db/_episodic.py +2 -4
- package/src/db/_reminders.py +45 -6
- package/src/db/_schema.py +31 -0
- package/src/evolution_cycle.py +33 -11
- package/src/hooks/auto_capture.py +1 -1
- package/src/hooks/capture-tool-logs.sh +76 -0
- package/src/hooks/inbox-hook.sh +2 -1
- package/src/hooks/post-compact.sh +2 -1
- package/src/hooks/pre-compact.sh +104 -2
- package/src/hooks/session-start.sh +6 -2
- package/src/hooks/session-stop.sh +4 -2
- package/src/kg_populate.py +4 -1
- package/src/migrate_embeddings.py +4 -1
- package/src/plugin_loader.py +100 -34
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/agents.py +2 -2
- package/src/plugins/backup.py +5 -4
- package/src/plugins/core_rules.py +37 -16
- package/src/plugins/episodic_memory.py +14 -5
- package/src/plugins/evolution.py +6 -2
- package/src/plugins/guard.py +20 -11
- package/src/plugins/update.py +256 -0
- package/src/requirements.txt +12 -0
- package/src/scripts/check-context.py +8 -3
- package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/apply_findings.py +514 -169
- package/src/scripts/deep-sleep/collect.py +480 -0
- package/src/scripts/deep-sleep/extract-prompt.md +233 -0
- package/src/scripts/deep-sleep/extract.py +249 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +168 -0
- package/src/scripts/deep-sleep/synthesize.py +191 -0
- package/src/scripts/nexo-auto-update.py +4 -211
- package/src/scripts/nexo-backup.sh +5 -13
- package/src/scripts/nexo-brain-activation.sh +26 -26
- package/src/scripts/nexo-catchup.py +36 -25
- package/src/scripts/nexo-cognitive-decay.py +7 -3
- package/src/scripts/nexo-daily-self-audit.py +44 -15
- package/src/scripts/nexo-deep-sleep.sh +31 -16
- package/src/scripts/nexo-evolution-run.py +21 -11
- package/src/scripts/nexo-followup-hygiene.py +6 -4
- package/src/scripts/nexo-github-monitor.py +12 -8
- package/src/scripts/nexo-immune.py +6 -4
- package/src/scripts/nexo-inbox-hook.sh +2 -1
- package/src/scripts/nexo-install.py +4 -225
- package/src/scripts/nexo-learning-housekeep.py +7 -3
- package/src/scripts/nexo-learning-validator.py +1 -22
- package/src/scripts/nexo-migrate.py +9 -3
- package/src/scripts/nexo-postmortem-consolidator.py +17 -10
- package/src/scripts/nexo-pre-commit.py +3 -1
- package/src/scripts/nexo-prevent-sleep.sh +29 -0
- package/src/scripts/nexo-proactive-dashboard.py +5 -4
- package/src/scripts/nexo-runtime-preflight.py +59 -55
- package/src/scripts/nexo-send-email.py +1 -1
- package/src/scripts/nexo-send-reply.py +3 -1
- package/src/scripts/nexo-sleep.py +13 -9
- package/src/scripts/nexo-snapshot-restore.sh +2 -1
- package/src/scripts/nexo-synthesis.py +11 -7
- package/src/scripts/nexo-tcc-approve.sh +79 -0
- package/src/scripts/nexo-update.sh +161 -0
- package/src/scripts/nexo-watchdog-smoke.py +18 -13
- package/src/scripts/nexo-watchdog.sh +22 -13
- package/src/server.py +77 -28
- package/src/storage_router.py +6 -2
- package/src/tools_learnings.py +6 -6
- package/src/tools_menu.py +1 -1
- package/src/tools_reminders_crud.py +10 -8
- package/src/tools_sessions.py +76 -4
- package/templates/CLAUDE.md.template +14 -80
- package/templates/launchagents/README.md +7 -7
- package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
- package/templates/launchagents/com.nexo.catchup.plist +4 -0
- package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
- package/templates/launchagents/com.nexo.dashboard.plist +5 -1
- package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
- package/templates/launchagents/com.nexo.evolution.plist +4 -0
- package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
- package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
- package/templates/launchagents/com.nexo.immune.plist +4 -0
- package/templates/launchagents/com.nexo.postmortem.plist +4 -0
- package/templates/launchagents/com.nexo.self-audit.plist +4 -0
- package/templates/launchagents/com.nexo.synthesis.plist +4 -0
- package/templates/launchagents/com.nexo.watchdog.plist +4 -0
- package/templates/openclaw.json +1 -1
- package/tests/conftest.py +2 -2
- package/tests/test_cognitive.py +7 -6
- package/tests/test_migrations.py +26 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
- package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/analyze_session.py +0 -217
- package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
- package/src/scripts/deep-sleep/prompt.md +0 -109
- package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
package/src/db/_reminders.py
CHANGED
|
@@ -69,14 +69,16 @@ def get_reminders(filter_type: str = 'all') -> list[dict]:
|
|
|
69
69
|
elif filter_type == 'due':
|
|
70
70
|
rows = conn.execute(
|
|
71
71
|
"SELECT * FROM reminders WHERE status NOT LIKE 'COMPLETED%' "
|
|
72
|
-
"AND status
|
|
72
|
+
"AND status NOT IN ('DELETED','archived','blocked','waiting') "
|
|
73
|
+
"AND date IS NOT NULL AND date <= ? "
|
|
73
74
|
"ORDER BY date ASC",
|
|
74
75
|
(today,)
|
|
75
76
|
).fetchall()
|
|
76
77
|
else: # 'all' — active only
|
|
77
78
|
rows = conn.execute(
|
|
78
79
|
"SELECT * FROM reminders WHERE status NOT LIKE 'COMPLETED%' "
|
|
79
|
-
"AND status
|
|
80
|
+
"AND status NOT IN ('DELETED','archived','blocked','waiting') "
|
|
81
|
+
"ORDER BY date ASC NULLS LAST"
|
|
80
82
|
).fetchall()
|
|
81
83
|
return [dict(r) for r in rows]
|
|
82
84
|
|
|
@@ -100,7 +102,7 @@ def find_similar_followups(description: str, threshold: float = 0.3) -> list[dic
|
|
|
100
102
|
conn = get_db()
|
|
101
103
|
rows = conn.execute(
|
|
102
104
|
"SELECT * FROM followups WHERE status NOT LIKE 'COMPLETED%' "
|
|
103
|
-
"AND status
|
|
105
|
+
"AND status NOT IN ('DELETED','archived','blocked','waiting')"
|
|
104
106
|
).fetchall()
|
|
105
107
|
|
|
106
108
|
def tokenize(text: str) -> set:
|
|
@@ -134,11 +136,22 @@ def create_followup(id: str, description: str, date: str = None,
|
|
|
134
136
|
reasoning: str = '', recurrence: str = None) -> dict:
|
|
135
137
|
"""Create a new followup with optional reasoning and recurrence.
|
|
136
138
|
|
|
139
|
+
Checks for similar open followups before creating. If a match is found,
|
|
140
|
+
returns a warning with the existing followup ID (still creates the new one).
|
|
141
|
+
|
|
137
142
|
recurrence format: 'weekly:monday', 'monthly:1', 'monthly:10', 'quarterly', etc.
|
|
138
143
|
When a recurring followup is completed, a new one is auto-created with the next date.
|
|
139
144
|
"""
|
|
140
145
|
conn = get_db()
|
|
141
146
|
now = now_epoch()
|
|
147
|
+
|
|
148
|
+
# Anti-duplicate check
|
|
149
|
+
similar = find_similar_followups(description)
|
|
150
|
+
warning = ""
|
|
151
|
+
if similar:
|
|
152
|
+
ids = ", ".join(s["id"] for s in similar[:3])
|
|
153
|
+
warning = f" ⚠ SIMILAR FOLLOWUPS EXIST: {ids} (scores: {', '.join(str(s['_similarity']) for s in similar[:3])}). Consider updating instead."
|
|
154
|
+
|
|
142
155
|
try:
|
|
143
156
|
conn.execute(
|
|
144
157
|
"INSERT INTO followups (id, date, description, verification, status, reasoning, recurrence, created_at, updated_at) "
|
|
@@ -150,7 +163,10 @@ def create_followup(id: str, description: str, date: str = None,
|
|
|
150
163
|
except sqlite3.IntegrityError:
|
|
151
164
|
return {"error": f"Followup {id} already exists. Use update instead."}
|
|
152
165
|
row = conn.execute("SELECT * FROM followups WHERE id = ?", (id,)).fetchone()
|
|
153
|
-
|
|
166
|
+
result = dict(row)
|
|
167
|
+
if warning:
|
|
168
|
+
result["warning"] = warning
|
|
169
|
+
return result
|
|
154
170
|
|
|
155
171
|
|
|
156
172
|
def update_followup(id: str, **kwargs) -> dict:
|
|
@@ -245,6 +261,18 @@ def complete_followup(id: str, result: str = '') -> dict:
|
|
|
245
261
|
archived_id = f"{id}-{today}"
|
|
246
262
|
conn.execute("UPDATE followups SET id = ? WHERE id = ?", (archived_id, id))
|
|
247
263
|
conn.commit()
|
|
264
|
+
|
|
265
|
+
# Fix FTS: remove old entry for original ID, add entry for archived ID
|
|
266
|
+
conn.execute("DELETE FROM unified_search WHERE source = 'followup' AND source_id = ?", (id,))
|
|
267
|
+
archived_row = conn.execute("SELECT * FROM followups WHERE id = ?", (archived_id,)).fetchone()
|
|
268
|
+
if archived_row:
|
|
269
|
+
fts_upsert(
|
|
270
|
+
"followup", archived_id, archived_id,
|
|
271
|
+
f"{archived_row['description']} {archived_row['verification'] or ''} {archived_row['reasoning'] or ''}",
|
|
272
|
+
"followup", commit=False,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# create_followup handles its own FTS entry for the new recurring ID
|
|
248
276
|
create_followup(
|
|
249
277
|
id=id,
|
|
250
278
|
description=row["description"],
|
|
@@ -254,6 +282,15 @@ def complete_followup(id: str, result: str = '') -> dict:
|
|
|
254
282
|
recurrence=recurrence,
|
|
255
283
|
)
|
|
256
284
|
|
|
285
|
+
# Return accurate result: the completed one is now archived_id, not id
|
|
286
|
+
return {
|
|
287
|
+
"id": archived_id,
|
|
288
|
+
"status": "COMPLETED",
|
|
289
|
+
"recurrence": recurrence,
|
|
290
|
+
"next_id": id,
|
|
291
|
+
"next_date": next_date,
|
|
292
|
+
}
|
|
293
|
+
|
|
257
294
|
return update_result
|
|
258
295
|
|
|
259
296
|
|
|
@@ -278,14 +315,16 @@ def get_followups(filter_type: str = 'all') -> list[dict]:
|
|
|
278
315
|
elif filter_type == 'due':
|
|
279
316
|
rows = conn.execute(
|
|
280
317
|
"SELECT * FROM followups WHERE status NOT LIKE 'COMPLETED%' "
|
|
281
|
-
"AND status
|
|
318
|
+
"AND status NOT IN ('DELETED','archived','blocked','waiting') "
|
|
319
|
+
"AND date IS NOT NULL AND date <= ? "
|
|
282
320
|
"ORDER BY date ASC",
|
|
283
321
|
(today,)
|
|
284
322
|
).fetchall()
|
|
285
323
|
else: # 'all' — active only
|
|
286
324
|
rows = conn.execute(
|
|
287
325
|
"SELECT * FROM followups WHERE status NOT LIKE 'COMPLETED%' "
|
|
288
|
-
"AND status
|
|
326
|
+
"AND status NOT IN ('DELETED','archived','blocked','waiting') "
|
|
327
|
+
"ORDER BY date ASC NULLS LAST"
|
|
289
328
|
).fetchall()
|
|
290
329
|
return [dict(r) for r in rows]
|
|
291
330
|
|
package/src/db/_schema.py
CHANGED
|
@@ -265,6 +265,36 @@ def _m14_learnings_priority_weight(conn):
|
|
|
265
265
|
_migrate_add_column(conn, "followups", "priority", "TEXT DEFAULT 'medium'")
|
|
266
266
|
|
|
267
267
|
|
|
268
|
+
def _m15_core_rules_tables(conn):
|
|
269
|
+
"""Core rules and version tracking tables for the core_rules plugin."""
|
|
270
|
+
conn.execute("""
|
|
271
|
+
CREATE TABLE IF NOT EXISTS core_rules (
|
|
272
|
+
id TEXT PRIMARY KEY,
|
|
273
|
+
category TEXT NOT NULL,
|
|
274
|
+
rule TEXT NOT NULL,
|
|
275
|
+
why TEXT NOT NULL,
|
|
276
|
+
importance INTEGER NOT NULL DEFAULT 3,
|
|
277
|
+
type TEXT NOT NULL DEFAULT 'advisory',
|
|
278
|
+
added_in TEXT DEFAULT '',
|
|
279
|
+
removed_in TEXT DEFAULT NULL,
|
|
280
|
+
is_active INTEGER NOT NULL DEFAULT 1
|
|
281
|
+
)
|
|
282
|
+
""")
|
|
283
|
+
conn.execute("""
|
|
284
|
+
CREATE TABLE IF NOT EXISTS core_rules_version (
|
|
285
|
+
id INTEGER PRIMARY KEY,
|
|
286
|
+
version TEXT NOT NULL DEFAULT '0.0.0',
|
|
287
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
288
|
+
)
|
|
289
|
+
""")
|
|
290
|
+
# Seed the version row so UPDATE statements in the plugin always find it
|
|
291
|
+
conn.execute(
|
|
292
|
+
"INSERT OR IGNORE INTO core_rules_version (id, version) VALUES (1, '0.0.0')"
|
|
293
|
+
)
|
|
294
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_category ON core_rules(category)")
|
|
295
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_active ON core_rules(is_active)")
|
|
296
|
+
|
|
297
|
+
|
|
268
298
|
# Migration registry — APPEND ONLY, never reorder or delete
|
|
269
299
|
MIGRATIONS = [
|
|
270
300
|
(1, "learnings_columns", _m1_learnings_columns),
|
|
@@ -281,6 +311,7 @@ MIGRATIONS = [
|
|
|
281
311
|
(12, "session_checkpoints", _m12_session_checkpoints),
|
|
282
312
|
(13, "claude_session_id", _m13_claude_session_id),
|
|
283
313
|
(14, "learnings_priority_weight", _m14_learnings_priority_weight),
|
|
314
|
+
(15, "core_rules_tables", _m15_core_rules_tables),
|
|
284
315
|
]
|
|
285
316
|
|
|
286
317
|
|
package/src/evolution_cycle.py
CHANGED
|
@@ -14,14 +14,22 @@ import time
|
|
|
14
14
|
from datetime import datetime, date, timedelta
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
SANDBOX_DIR =
|
|
21
|
-
SNAPSHOTS_DIR =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
18
|
+
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(NEXO_HOME)))
|
|
19
|
+
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
20
|
+
SANDBOX_DIR = NEXO_HOME / "sandbox" / "workspace"
|
|
21
|
+
SNAPSHOTS_DIR = NEXO_HOME / "snapshots"
|
|
22
|
+
RESTORE_LOG = NEXO_HOME / "logs" / "snapshot-restores.log"
|
|
23
|
+
|
|
24
|
+
# Evolution config: brain/ (canonical) > cortex/ (legacy) > NEXO_CODE (dev)
|
|
25
|
+
def _resolve_evolution_file(name: str) -> Path:
|
|
26
|
+
for candidate in [NEXO_HOME / "brain" / name, NEXO_HOME / "cortex" / name, NEXO_CODE / name]:
|
|
27
|
+
if candidate.exists():
|
|
28
|
+
return candidate
|
|
29
|
+
return NEXO_HOME / "brain" / name # default canonical path
|
|
30
|
+
|
|
31
|
+
OBJECTIVE_FILE = _resolve_evolution_file("evolution-objective.json")
|
|
32
|
+
PROMPT_FILE = _resolve_evolution_file("evolution-prompt.md")
|
|
25
33
|
|
|
26
34
|
MAX_SNAPSHOTS = 8
|
|
27
35
|
|
|
@@ -105,6 +113,8 @@ def create_snapshot(files_to_backup: list) -> str:
|
|
|
105
113
|
rel = str(fp).replace(str(Path.home()) + "/", "")
|
|
106
114
|
dest = files_dir / rel
|
|
107
115
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
if os.path.abspath(str(fp)) == os.path.abspath(str(dest)):
|
|
117
|
+
continue # Skip: source and destination are the same file
|
|
108
118
|
shutil.copy2(fp, dest)
|
|
109
119
|
manifest["files"].append(rel)
|
|
110
120
|
|
|
@@ -144,9 +154,21 @@ def dry_run_restore_test() -> bool:
|
|
|
144
154
|
|
|
145
155
|
test_file.write_text("modified_content")
|
|
146
156
|
|
|
157
|
+
# Find restore script: NEXO_CODE/scripts/ first, then NEXO_HOME/scripts/
|
|
158
|
+
_nexo_code = Path(os.environ.get("NEXO_CODE", ""))
|
|
159
|
+
restore_script = None
|
|
160
|
+
for candidate in [_nexo_code / "scripts" / "nexo-snapshot-restore.sh",
|
|
161
|
+
NEXO_HOME / "scripts" / "nexo-snapshot-restore.sh"]:
|
|
162
|
+
if candidate.exists():
|
|
163
|
+
restore_script = candidate
|
|
164
|
+
break
|
|
165
|
+
if not restore_script:
|
|
166
|
+
test_file.unlink(missing_ok=True)
|
|
167
|
+
return False # No restore script available
|
|
168
|
+
|
|
147
169
|
try:
|
|
148
170
|
subprocess.run(
|
|
149
|
-
[str(
|
|
171
|
+
[str(restore_script), snap_dir],
|
|
150
172
|
capture_output=True, timeout=10, check=True
|
|
151
173
|
)
|
|
152
174
|
content = test_file.read_text()
|
|
@@ -200,7 +222,7 @@ INVESTIGATE using these tools:
|
|
|
200
222
|
4. Read ~/.nexo/coordination/postmortem-daily.md — self-critique patterns
|
|
201
223
|
5. Read ~/.nexo/logs/self-audit-summary.json — system health
|
|
202
224
|
6. Glob ~/.nexo/scripts/*.py — existing scripts
|
|
203
|
-
7. Glob ~/.nexo/
|
|
225
|
+
7. Glob ~/.nexo/plugins/*.py — existing plugins
|
|
204
226
|
|
|
205
227
|
LOOK FOR:
|
|
206
228
|
- Repeated errors that guard isn't preventing
|
|
@@ -210,7 +232,7 @@ LOOK FOR:
|
|
|
210
232
|
- Patterns in self-critique that suggest systemic issues
|
|
211
233
|
|
|
212
234
|
SAFETY:
|
|
213
|
-
- Safe zones for auto changes: ~/.nexo/scripts/, ~/.nexo/
|
|
235
|
+
- Safe zones for auto changes: ~/.nexo/scripts/, ~/.nexo/plugins/, ~/.nexo/brain/
|
|
214
236
|
- IMMUTABLE files (never touch): db.py, server.py, plugin_loader.py, cognitive.py, CLAUDE.md
|
|
215
237
|
- Every change needs: what file, what to change, why, risk, how to verify
|
|
216
238
|
|
|
@@ -40,6 +40,82 @@ record = {
|
|
|
40
40
|
print(json.dumps(record))
|
|
41
41
|
" >> "$LOG_FILE" 2>/dev/null
|
|
42
42
|
|
|
43
|
+
# ── Layer 1: Auto-diary every 10 tool calls ─────────────────────────
|
|
44
|
+
COUNTER_FILE="$NEXO_HOME/operations/.tool-call-count"
|
|
45
|
+
NEXO_DB="$NEXO_HOME/data/nexo.db"
|
|
46
|
+
|
|
47
|
+
# Increment counter (atomic: read+write in one step)
|
|
48
|
+
COUNT=1
|
|
49
|
+
if [ -f "$COUNTER_FILE" ]; then
|
|
50
|
+
COUNT=$(( $(cat "$COUNTER_FILE" 2>/dev/null || echo 0) + 1 ))
|
|
51
|
+
fi
|
|
52
|
+
echo "$COUNT" > "$COUNTER_FILE"
|
|
53
|
+
|
|
54
|
+
# Every 10 tool calls, write a mechanical diary draft to SQLite
|
|
55
|
+
if [ $(( COUNT % 10 )) -eq 0 ] && [ -f "$NEXO_DB" ]; then
|
|
56
|
+
python3 -c "
|
|
57
|
+
import json, sqlite3, os, sys
|
|
58
|
+
from datetime import datetime
|
|
59
|
+
|
|
60
|
+
db_path = '$NEXO_DB'
|
|
61
|
+
log_file = '$LOG_FILE'
|
|
62
|
+
count = $COUNT
|
|
63
|
+
|
|
64
|
+
# Read last 10 tool calls from today's log
|
|
65
|
+
entries = []
|
|
66
|
+
if os.path.isfile(log_file):
|
|
67
|
+
with open(log_file, 'r') as f:
|
|
68
|
+
lines = f.readlines()
|
|
69
|
+
for line in lines[-10:]:
|
|
70
|
+
try:
|
|
71
|
+
e = json.loads(line.strip())
|
|
72
|
+
name = e.get('tool_name', '?')
|
|
73
|
+
inp = e.get('tool_input', {})
|
|
74
|
+
# Brief args: first key's value, truncated
|
|
75
|
+
brief = ''
|
|
76
|
+
if isinstance(inp, dict):
|
|
77
|
+
for k, v in list(inp.items())[:1]:
|
|
78
|
+
brief = str(v)[:60]
|
|
79
|
+
entries.append(f'{name}({brief})')
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if not entries:
|
|
84
|
+
sys.exit(0)
|
|
85
|
+
|
|
86
|
+
tools_summary = ', '.join(entries[-10:])
|
|
87
|
+
|
|
88
|
+
# Get current session and task from sessions table
|
|
89
|
+
conn = sqlite3.connect(db_path, timeout=2)
|
|
90
|
+
conn.row_factory = sqlite3.Row
|
|
91
|
+
row = conn.execute(
|
|
92
|
+
'SELECT sid, task FROM sessions ORDER BY last_update_epoch DESC LIMIT 1'
|
|
93
|
+
).fetchone()
|
|
94
|
+
if not row:
|
|
95
|
+
conn.close()
|
|
96
|
+
sys.exit(0)
|
|
97
|
+
|
|
98
|
+
sid = row['sid']
|
|
99
|
+
task = row['task'] or 'unknown'
|
|
100
|
+
|
|
101
|
+
summary = f'[AUTO-{count}] {len(entries)} tool calls: {tools_summary[:250]}. Task: {task[:100]}'
|
|
102
|
+
|
|
103
|
+
# Write to session_diary_draft (UPSERT)
|
|
104
|
+
conn.execute('''
|
|
105
|
+
INSERT INTO session_diary_draft (sid, summary_draft, tasks_seen, change_ids, decision_ids, last_context_hint, heartbeat_count, updated_at)
|
|
106
|
+
VALUES (?, ?, '[]', '[]', '[]', ?, 0, datetime('now'))
|
|
107
|
+
ON CONFLICT(sid) DO UPDATE SET
|
|
108
|
+
summary_draft = excluded.summary_draft,
|
|
109
|
+
last_context_hint = excluded.last_context_hint,
|
|
110
|
+
updated_at = datetime('now')
|
|
111
|
+
''', (sid, summary, f'auto-diary at {count} tool calls'))
|
|
112
|
+
conn.commit()
|
|
113
|
+
conn.close()
|
|
114
|
+
" 2>/dev/null &
|
|
115
|
+
# Reset counter after writing
|
|
116
|
+
echo "0" > "$COUNTER_FILE"
|
|
117
|
+
fi
|
|
118
|
+
|
|
43
119
|
# Cleanup: delete logs >= 30 days old (once daily, uses marker file)
|
|
44
120
|
CLEANUP_MARKER="$LOG_DIR/.last-cleanup"
|
|
45
121
|
if [ ! -f "$CLEANUP_MARKER" ] || [ "$(cat "$CLEANUP_MARKER" 2>/dev/null)" != "$TODAY" ]; then
|
package/src/hooks/inbox-hook.sh
CHANGED
|
@@ -27,7 +27,8 @@ echo "$NOW" > "$DEBOUNCE_FILE"
|
|
|
27
27
|
|
|
28
28
|
# 4. Find NEXO SID mapped to this Claude session_id
|
|
29
29
|
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
30
|
-
DB="$NEXO_HOME/nexo.db"
|
|
30
|
+
DB="$NEXO_HOME/data/nexo.db"
|
|
31
|
+
mkdir -p "$NEXO_HOME/data"
|
|
31
32
|
[ -f "$DB" ] || exit 0
|
|
32
33
|
|
|
33
34
|
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)
|
package/src/hooks/pre-compact.sh
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
8
|
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
9
|
-
NEXO_DB="$NEXO_HOME/nexo.db"
|
|
9
|
+
NEXO_DB="$NEXO_HOME/data/nexo.db"
|
|
10
|
+
mkdir -p "$NEXO_HOME/data"
|
|
10
11
|
TODAY=$(date +%Y-%m-%d)
|
|
11
12
|
LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
|
|
12
13
|
LOG_LINES=0
|
|
@@ -42,8 +43,109 @@ if [ -f "$NEXO_DB" ]; then
|
|
|
42
43
|
fi
|
|
43
44
|
fi
|
|
44
45
|
|
|
46
|
+
# ── Layer 2: Emergency auto-diary before compaction ──────────────────
|
|
47
|
+
# Write an actual session_diary entry (not draft) with mechanical summary
|
|
48
|
+
# This is the parachute — if the LLM never wrote a diary, at least this exists
|
|
49
|
+
if [ -f "$NEXO_DB" ]; then
|
|
50
|
+
python3 -c "
|
|
51
|
+
import json, sqlite3, os, sys
|
|
52
|
+
from datetime import datetime
|
|
53
|
+
|
|
54
|
+
db_path = '$NEXO_DB'
|
|
55
|
+
log_file = '$LOG_FILE'
|
|
56
|
+
|
|
57
|
+
conn = sqlite3.connect(db_path, timeout=3)
|
|
58
|
+
conn.row_factory = sqlite3.Row
|
|
59
|
+
|
|
60
|
+
# Get latest active session
|
|
61
|
+
row = conn.execute(
|
|
62
|
+
'SELECT sid, task FROM sessions ORDER BY last_update_epoch DESC LIMIT 1'
|
|
63
|
+
).fetchone()
|
|
64
|
+
if not row:
|
|
65
|
+
conn.close()
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
sid = row['sid']
|
|
69
|
+
task = row['task'] or 'unknown'
|
|
70
|
+
|
|
71
|
+
# Check if a real diary already exists for this session
|
|
72
|
+
has_diary = conn.execute(
|
|
73
|
+
'SELECT id FROM session_diary WHERE session_id = ? LIMIT 1', (sid,)
|
|
74
|
+
).fetchone()
|
|
75
|
+
if has_diary:
|
|
76
|
+
conn.close()
|
|
77
|
+
sys.exit(0) # LLM already wrote one, no need for emergency diary
|
|
78
|
+
|
|
79
|
+
# Find last diary timestamp to know where to start reading logs
|
|
80
|
+
last_diary = conn.execute(
|
|
81
|
+
'SELECT created_at FROM session_diary ORDER BY created_at DESC LIMIT 1'
|
|
82
|
+
).fetchone()
|
|
83
|
+
last_diary_ts = last_diary['created_at'] if last_diary else '1970-01-01T00:00:00Z'
|
|
84
|
+
|
|
85
|
+
# Read tool log entries since last diary
|
|
86
|
+
entries = []
|
|
87
|
+
modified_files = []
|
|
88
|
+
git_actions = []
|
|
89
|
+
if os.path.isfile(log_file):
|
|
90
|
+
with open(log_file, 'r') as f:
|
|
91
|
+
for line in f:
|
|
92
|
+
try:
|
|
93
|
+
e = json.loads(line.strip())
|
|
94
|
+
ts = e.get('timestamp', '')
|
|
95
|
+
if ts < last_diary_ts:
|
|
96
|
+
continue
|
|
97
|
+
name = e.get('tool_name', '?')
|
|
98
|
+
inp = e.get('tool_input', {}) or {}
|
|
99
|
+
brief = ''
|
|
100
|
+
if isinstance(inp, dict):
|
|
101
|
+
for k, v in list(inp.items())[:1]:
|
|
102
|
+
brief = str(v)[:80]
|
|
103
|
+
entries.append(f'{name}({brief})')
|
|
104
|
+
# Extract decisions from tool calls
|
|
105
|
+
if name in ('Edit', 'Write'):
|
|
106
|
+
fp = inp.get('file_path', inp.get('path', ''))
|
|
107
|
+
if fp:
|
|
108
|
+
modified_files.append(fp.split('/')[-1])
|
|
109
|
+
if name == 'Bash':
|
|
110
|
+
cmd = str(inp.get('command', ''))
|
|
111
|
+
if 'git commit' in cmd or 'git push' in cmd:
|
|
112
|
+
git_actions.append(cmd[:80])
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
if not entries:
|
|
117
|
+
conn.close()
|
|
118
|
+
sys.exit(0)
|
|
119
|
+
|
|
120
|
+
# Build mechanical diary
|
|
121
|
+
tools_summary = ', '.join(entries[-30:])[:500]
|
|
122
|
+
summary = f'[EMERGENCY PRE-COMPACT] {len(entries)} tool calls since last diary. Tools: {tools_summary}'
|
|
123
|
+
|
|
124
|
+
decisions = ''
|
|
125
|
+
if modified_files:
|
|
126
|
+
decisions = 'Modified: ' + ', '.join(set(modified_files))[:300]
|
|
127
|
+
if git_actions:
|
|
128
|
+
decisions += (' | Git: ' + ', '.join(git_actions))[:200]
|
|
129
|
+
if not decisions:
|
|
130
|
+
decisions = 'No file modifications detected in tool logs'
|
|
131
|
+
|
|
132
|
+
pending = f'Current task: {task[:200]}'
|
|
133
|
+
context_next = 'COMPACTION HAPPENED. Read this diary to continue. Check session_checkpoints and tool-logs for full context.'
|
|
134
|
+
|
|
135
|
+
# Write actual session_diary entry
|
|
136
|
+
conn.execute('''
|
|
137
|
+
INSERT INTO session_diary
|
|
138
|
+
(session_id, decisions, discarded, pending, context_next,
|
|
139
|
+
mental_state, domain, user_signals, summary, source)
|
|
140
|
+
VALUES (?, ?, '', ?, ?, 'auto-generated', 'auto', '', ?, 'pre-compact-hook')
|
|
141
|
+
''', (sid, decisions, pending, context_next, summary))
|
|
142
|
+
conn.commit()
|
|
143
|
+
conn.close()
|
|
144
|
+
" 2>/dev/null || true
|
|
145
|
+
fi
|
|
146
|
+
|
|
45
147
|
cat << HOOKEOF
|
|
46
148
|
{
|
|
47
|
-
"systemMessage": "CONTEXT IS ABOUT TO BE COMPRESSED.\n\nOBLIGATORY ACTIONS BEFORE COMPACTION:\n1. Save critical state via MCP: nexo_checkpoint_save with current task, active files, decisions, errors, next step, and reasoning thread.\n2. If there is work in progress without a commit, save data via nexo_entity_create, nexo_preference_set, nexo_learning_add, nexo_followup_create.\n3. PERSISTENT TOOL LOGS: ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl has ${LOG_LINES} entries.\n4. After compaction, the PostCompact hook will re-inject a Core Memory Block with the checkpoint.\n5. MCP tools (nexo_*) preserve all state — use them to recover context."
|
|
149
|
+
"systemMessage": "CONTEXT IS ABOUT TO BE COMPRESSED.\n\nOBLIGATORY ACTIONS BEFORE COMPACTION:\n1. Save critical state via MCP: nexo_checkpoint_save with current task, active files, decisions, errors, next step, and reasoning thread.\n2. If there is work in progress without a commit, save data via nexo_entity_create, nexo_preference_set, nexo_learning_add, nexo_followup_create.\n3. PERSISTENT TOOL LOGS: ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl has ${LOG_LINES} entries.\n4. After compaction, the PostCompact hook will re-inject a Core Memory Block with the checkpoint.\n5. MCP tools (nexo_*) preserve all state — use them to recover context.\n6. EMERGENCY DIARY: An automatic diary was written by the pre-compact hook. The LLM can still write a better one via nexo_session_diary_write."
|
|
48
150
|
}
|
|
49
151
|
HOOKEOF
|
|
@@ -48,7 +48,7 @@ from datetime import date
|
|
|
48
48
|
today_str = '$TODAY'
|
|
49
49
|
weekday = '$WEEKDAY'
|
|
50
50
|
nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
|
|
51
|
-
db_path = os.path.join(nexo_home, 'nexo.db')
|
|
51
|
+
db_path = os.path.join(nexo_home, 'data', 'nexo.db')
|
|
52
52
|
|
|
53
53
|
lines = []
|
|
54
54
|
lines.append(f'## Date: {today_str} ({weekday})')
|
|
@@ -212,7 +212,11 @@ except Exception as e:
|
|
|
212
212
|
fi
|
|
213
213
|
|
|
214
214
|
# ─── Cortex Report: what happened while user was away ───
|
|
215
|
-
|
|
215
|
+
# Check brain/ (canonical) first, fall back to cortex/ (legacy)
|
|
216
|
+
CORTEX_BRIEFING="$NEXO_HOME/brain/last-briefing.json"
|
|
217
|
+
if [ ! -f "$CORTEX_BRIEFING" ] && [ -f "$NEXO_HOME/cortex/last-briefing.json" ]; then
|
|
218
|
+
CORTEX_BRIEFING="$NEXO_HOME/cortex/last-briefing.json"
|
|
219
|
+
fi
|
|
216
220
|
if [ -f "$CORTEX_BRIEFING" ]; then
|
|
217
221
|
CORTEX_SECTION=$(python3 -c "
|
|
218
222
|
import json
|
|
@@ -25,7 +25,8 @@ TOOL_LOG="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
|
|
|
25
25
|
python3 -c "
|
|
26
26
|
import sys, json, os
|
|
27
27
|
nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
|
|
28
|
-
|
|
28
|
+
nexo_code = os.environ.get('NEXO_CODE', nexo_home)
|
|
29
|
+
sys.path.insert(0, nexo_code)
|
|
29
30
|
os.environ['NEXO_SKIP_FS_INDEX'] = '1'
|
|
30
31
|
from db import init_db, get_db, get_active_sessions, upsert_diary_draft, get_diary_draft
|
|
31
32
|
init_db()
|
|
@@ -57,7 +58,8 @@ SESSION_START_TS="$NEXO_HOME/operations/.session-start-ts"
|
|
|
57
58
|
# 0.5. Detect non-interactive (claude -p) sessions — skip post-mortem entirely
|
|
58
59
|
# SessionStart hook writes .session-start-ts. If missing or stale (>30 min),
|
|
59
60
|
# this is likely a -p script session — approve immediately.
|
|
60
|
-
|
|
61
|
+
# Also skip if NEXO_HEADLESS=1 is set (explicit headless mode for scripts).
|
|
62
|
+
if [ "${NEXO_HEADLESS:-}" = "1" ] || [ ! -f "$SESSION_START_TS" ] || [ "$(($(date +%s) - $(cat "$SESSION_START_TS" 2>/dev/null || echo 0)))" -gt 1800 ]; then
|
|
61
63
|
cat << 'HOOKEOF'
|
|
62
64
|
{
|
|
63
65
|
"decision": "approve"
|
package/src/kg_populate.py
CHANGED
|
@@ -13,7 +13,10 @@ from db import get_db
|
|
|
13
13
|
|
|
14
14
|
def _cognitive_db():
|
|
15
15
|
"""Direct cognitive.db connection (for somatic_markers)."""
|
|
16
|
-
|
|
16
|
+
nexo_home = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
17
|
+
data_dir = os.path.join(nexo_home, "data")
|
|
18
|
+
os.makedirs(data_dir, exist_ok=True)
|
|
19
|
+
path = os.path.join(data_dir, "cognitive.db")
|
|
17
20
|
conn = sqlite3.connect(path)
|
|
18
21
|
conn.row_factory = sqlite3.Row
|
|
19
22
|
return conn
|
|
@@ -15,7 +15,10 @@ import sys
|
|
|
15
15
|
import time
|
|
16
16
|
import numpy as np
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
NEXO_HOME = os.environ.get("NEXO_HOME", os.path.expanduser("~/.nexo"))
|
|
19
|
+
_data_dir = os.path.join(NEXO_HOME, "data")
|
|
20
|
+
os.makedirs(_data_dir, exist_ok=True)
|
|
21
|
+
DB_PATH = os.path.join(_data_dir, "cognitive.db")
|
|
19
22
|
BACKUP_PATH = DB_PATH + ".bak-384dims-pre-upgrade"
|
|
20
23
|
|
|
21
24
|
MODELS = {
|