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