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,151 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# NEXO PreCompact Hook — Save checkpoint + inject preservation instructions
|
|
3
|
-
# This runs BEFORE Claude Code compacts. It:
|
|
4
|
-
# 1. Enriches the session checkpoint in SQLite with latest diary draft data
|
|
5
|
-
# 2. Injects a systemMessage telling the operator to save any WIP via MCP tools
|
|
6
|
-
set -uo pipefail
|
|
7
|
-
|
|
8
|
-
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
9
|
-
NEXO_DB="$NEXO_HOME/data/nexo.db"
|
|
10
|
-
mkdir -p "$NEXO_HOME/data"
|
|
11
|
-
TODAY=$(date +%Y-%m-%d)
|
|
12
|
-
LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
|
|
13
|
-
LOG_LINES=0
|
|
14
|
-
if [ -f "$LOG_FILE" ]; then
|
|
15
|
-
LOG_LINES=$(wc -l < "$LOG_FILE" | tr -d ' ')
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
# Enrich checkpoint: copy diary draft context into checkpoint if exists
|
|
19
|
-
if [ -f "$NEXO_DB" ]; then
|
|
20
|
-
# Get latest active session's diary draft
|
|
21
|
-
LATEST_SID=$(sqlite3 "$NEXO_DB" "
|
|
22
|
-
SELECT sid FROM sessions ORDER BY last_update_epoch DESC LIMIT 1
|
|
23
|
-
" 2>/dev/null || echo "")
|
|
24
|
-
|
|
25
|
-
if [ -n "$LATEST_SID" ]; then
|
|
26
|
-
# Write SID to temp file so PostCompact knows which session compacted
|
|
27
|
-
echo "$LATEST_SID" > /tmp/nexo-compacting-sid
|
|
28
|
-
# Pull diary draft data into checkpoint
|
|
29
|
-
sqlite3 "$NEXO_DB" "
|
|
30
|
-
INSERT INTO session_checkpoints (sid, task, current_goal, updated_at)
|
|
31
|
-
SELECT s.sid, s.task, COALESCE(d.last_context_hint, s.task), datetime('now')
|
|
32
|
-
FROM sessions s
|
|
33
|
-
LEFT JOIN session_diary_draft d ON d.sid = s.sid
|
|
34
|
-
WHERE s.sid = '$LATEST_SID'
|
|
35
|
-
ON CONFLICT(sid) DO UPDATE SET
|
|
36
|
-
task = excluded.task,
|
|
37
|
-
current_goal = CASE
|
|
38
|
-
WHEN excluded.current_goal != '' THEN excluded.current_goal
|
|
39
|
-
ELSE session_checkpoints.current_goal
|
|
40
|
-
END,
|
|
41
|
-
updated_at = datetime('now')
|
|
42
|
-
" 2>/dev/null || true
|
|
43
|
-
fi
|
|
44
|
-
fi
|
|
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
|
-
|
|
147
|
-
cat << HOOKEOF
|
|
148
|
-
{
|
|
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."
|
|
150
|
-
}
|
|
151
|
-
HOOKEOF
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# NEXO SessionStart hook — generates a comprehensive briefing
|
|
3
|
-
# Reads SQLite directly for reminders, followups, active sessions.
|
|
4
|
-
# Caches output for 1 hour to avoid regenerating on rapid successive sessions.
|
|
5
|
-
set -uo pipefail
|
|
6
|
-
|
|
7
|
-
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
8
|
-
BRIEFING_FILE="$NEXO_HOME/coordination/session-briefing.txt"
|
|
9
|
-
MAX_AGE_SECONDS=3600 # 1 hour cache
|
|
10
|
-
|
|
11
|
-
mkdir -p "$NEXO_HOME/coordination" "$NEXO_HOME/operations"
|
|
12
|
-
|
|
13
|
-
# Write session start timestamp for session-scoped tool counting
|
|
14
|
-
date +%s > "$NEXO_HOME/operations/.session-start-ts"
|
|
15
|
-
|
|
16
|
-
# Clean up post-mortem flag from previous session
|
|
17
|
-
rm -f "$NEXO_HOME/operations/.postmortem-complete" 2>/dev/null
|
|
18
|
-
|
|
19
|
-
# Capture Claude Code session_id for inter-terminal inbox hook
|
|
20
|
-
HOOK_INPUT=$(cat || true)
|
|
21
|
-
CLAUDE_SID=""
|
|
22
|
-
if [ -n "$HOOK_INPUT" ]; then
|
|
23
|
-
CLAUDE_SID=$(echo "$HOOK_INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" 2>/dev/null || true)
|
|
24
|
-
fi
|
|
25
|
-
if [ -n "$CLAUDE_SID" ]; then
|
|
26
|
-
echo "$CLAUDE_SID" > "/tmp/nexo-claude-sid-${CLAUDE_SID}"
|
|
27
|
-
# Also write to a predictable location for the startup prompt
|
|
28
|
-
echo "$CLAUDE_SID" > "$NEXO_HOME/coordination/.claude-session-id"
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# If briefing exists and is less than 1 hour old, skip regeneration
|
|
32
|
-
if [ -f "$BRIEFING_FILE" ]; then
|
|
33
|
-
if [ "$(uname)" = "Darwin" ]; then
|
|
34
|
-
file_age=$(( $(date +%s) - $(stat -f %m "$BRIEFING_FILE") ))
|
|
35
|
-
else
|
|
36
|
-
file_age=$(( $(date +%s) - $(stat -c %Y "$BRIEFING_FILE") ))
|
|
37
|
-
fi
|
|
38
|
-
if [ "$file_age" -lt "$MAX_AGE_SECONDS" ]; then
|
|
39
|
-
exit 0
|
|
40
|
-
fi
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
TODAY=$(date +%Y-%m-%d)
|
|
44
|
-
WEEKDAY=$(date +%A)
|
|
45
|
-
|
|
46
|
-
# Generate briefing from SQLite
|
|
47
|
-
python3 -c "
|
|
48
|
-
import json, os, sys
|
|
49
|
-
from datetime import date
|
|
50
|
-
|
|
51
|
-
today_str = '$TODAY'
|
|
52
|
-
weekday = '$WEEKDAY'
|
|
53
|
-
nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
|
|
54
|
-
db_path = os.path.join(nexo_home, 'data', 'nexo.db')
|
|
55
|
-
|
|
56
|
-
lines = []
|
|
57
|
-
lines.append(f'## Date: {today_str} ({weekday})')
|
|
58
|
-
lines.append('')
|
|
59
|
-
|
|
60
|
-
# Read from SQLite
|
|
61
|
-
reminders_rows = []
|
|
62
|
-
followups_rows = []
|
|
63
|
-
sessions = []
|
|
64
|
-
sqlite_ok = True
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
import sqlite3
|
|
68
|
-
if not os.path.exists(db_path):
|
|
69
|
-
sqlite_ok = False
|
|
70
|
-
else:
|
|
71
|
-
db = sqlite3.connect(db_path, timeout=10)
|
|
72
|
-
db.execute('PRAGMA journal_mode=WAL')
|
|
73
|
-
db.execute('PRAGMA busy_timeout=10000')
|
|
74
|
-
db.row_factory = sqlite3.Row
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
reminders_rows = [dict(r) for r in db.execute(
|
|
78
|
-
'SELECT id, date, description, status, category FROM reminders '
|
|
79
|
-
'WHERE status NOT LIKE \"%COMPLET%\" AND status NOT LIKE \"%DELET%\" '
|
|
80
|
-
'AND status NOT LIKE \"%COMPLETED%\" AND status NOT LIKE \"%DELETED%\"'
|
|
81
|
-
).fetchall()]
|
|
82
|
-
except Exception:
|
|
83
|
-
pass
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
followups_rows = [dict(r) for r in db.execute(
|
|
87
|
-
'SELECT id, date, description, status FROM followups '
|
|
88
|
-
'WHERE status NOT LIKE \"%COMPLET%\" AND status NOT LIKE \"%COMPLETED%\"'
|
|
89
|
-
).fetchall()]
|
|
90
|
-
except Exception:
|
|
91
|
-
pass
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
rows = db.execute(
|
|
95
|
-
'SELECT sid, task, started FROM sessions '
|
|
96
|
-
'WHERE completed=0 AND (strftime(\"%s\",\"now\") - last_update) < 900'
|
|
97
|
-
).fetchall()
|
|
98
|
-
sessions = [{'sid': r['sid'], 'task': r['task'], 'started': r['started'][:16]} for r in rows]
|
|
99
|
-
except Exception:
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
|
-
db.close()
|
|
103
|
-
except Exception:
|
|
104
|
-
sqlite_ok = False
|
|
105
|
-
|
|
106
|
-
if not sqlite_ok:
|
|
107
|
-
lines.append('Database not initialized yet. Run nexo_startup to begin.')
|
|
108
|
-
lines.append('')
|
|
109
|
-
print('\n'.join(lines))
|
|
110
|
-
sys.exit(0)
|
|
111
|
-
|
|
112
|
-
# Overdue reminders
|
|
113
|
-
lines.append('## Overdue Reminders')
|
|
114
|
-
found = False
|
|
115
|
-
for r in reminders_rows:
|
|
116
|
-
rdate = r.get('date', '')
|
|
117
|
-
if rdate and rdate[:10] < today_str:
|
|
118
|
-
try:
|
|
119
|
-
delta = (date.fromisoformat(today_str) - date.fromisoformat(rdate[:10])).days
|
|
120
|
-
except:
|
|
121
|
-
delta = '?'
|
|
122
|
-
desc = (r.get('description', '') or '')[:120]
|
|
123
|
-
lines.append(f'- [{r[\"id\"]}] {rdate} {desc} -- {delta} day(s) overdue')
|
|
124
|
-
found = True
|
|
125
|
-
if not found:
|
|
126
|
-
lines.append('NONE')
|
|
127
|
-
lines.append('')
|
|
128
|
-
|
|
129
|
-
# Today's reminders
|
|
130
|
-
lines.append('## Reminders Due Today')
|
|
131
|
-
found = False
|
|
132
|
-
for r in reminders_rows:
|
|
133
|
-
rdate = r.get('date', '')
|
|
134
|
-
if rdate and rdate[:10] == today_str:
|
|
135
|
-
desc = (r.get('description', '') or '')[:120]
|
|
136
|
-
lines.append(f'- [{r[\"id\"]}] {desc}')
|
|
137
|
-
found = True
|
|
138
|
-
if not found:
|
|
139
|
-
lines.append('NONE')
|
|
140
|
-
lines.append('')
|
|
141
|
-
|
|
142
|
-
# Pending followups (due today or overdue)
|
|
143
|
-
lines.append('## Followups Due Today or Overdue')
|
|
144
|
-
found = False
|
|
145
|
-
for r in followups_rows:
|
|
146
|
-
fdate = r.get('date', '')
|
|
147
|
-
if fdate and fdate[:10] <= today_str:
|
|
148
|
-
desc = (r.get('description', '') or '')[:100]
|
|
149
|
-
lines.append(f'- [{r[\"id\"]}] {fdate} {desc}')
|
|
150
|
-
found = True
|
|
151
|
-
if not found:
|
|
152
|
-
lines.append('NONE')
|
|
153
|
-
lines.append('')
|
|
154
|
-
|
|
155
|
-
# Active sessions
|
|
156
|
-
lines.append('## Active Sessions')
|
|
157
|
-
if sessions:
|
|
158
|
-
for s in sessions:
|
|
159
|
-
lines.append(f'- [{s[\"sid\"]}] {s[\"task\"]} (since {s[\"started\"]})')
|
|
160
|
-
else:
|
|
161
|
-
lines.append('NONE')
|
|
162
|
-
lines.append('')
|
|
163
|
-
|
|
164
|
-
# Last self-audit
|
|
165
|
-
audit_file = os.path.join(nexo_home, 'logs', 'self-audit-summary.json')
|
|
166
|
-
if os.path.exists(audit_file):
|
|
167
|
-
try:
|
|
168
|
-
audit = json.load(open(audit_file))
|
|
169
|
-
lines.append('## Last Self-Audit')
|
|
170
|
-
lines.append(json.dumps(audit, indent=2)[:500])
|
|
171
|
-
lines.append('')
|
|
172
|
-
except Exception:
|
|
173
|
-
pass
|
|
174
|
-
|
|
175
|
-
print('\n'.join(lines))
|
|
176
|
-
" > "$BRIEFING_FILE" 2>/dev/null
|
|
177
|
-
|
|
178
|
-
# If generation failed, write minimal briefing
|
|
179
|
-
if [ ! -s "$BRIEFING_FILE" ]; then
|
|
180
|
-
echo "## Briefing unavailable — generation error. Use nexo_reminders MCP for fresh data." > "$BRIEFING_FILE"
|
|
181
|
-
fi
|
|
182
|
-
|
|
183
|
-
# ─── Semantic Context: recent work sessions ───
|
|
184
|
-
# Append recent session summaries for immediate context
|
|
185
|
-
CLAUDE_MEM_DB="$NEXO_HOME/claude-mem.db"
|
|
186
|
-
|
|
187
|
-
if [ -f "$CLAUDE_MEM_DB" ]; then
|
|
188
|
-
RECENT_SESSIONS=$(python3 -c "
|
|
189
|
-
import sqlite3, sys
|
|
190
|
-
try:
|
|
191
|
-
db = sqlite3.connect('$CLAUDE_MEM_DB')
|
|
192
|
-
rows = db.execute('''
|
|
193
|
-
SELECT created_at, request, learned, completed
|
|
194
|
-
FROM session_summaries
|
|
195
|
-
ORDER BY id DESC LIMIT 5
|
|
196
|
-
''').fetchall()
|
|
197
|
-
db.close()
|
|
198
|
-
if rows:
|
|
199
|
-
print()
|
|
200
|
-
print('## Last 5 Work Sessions')
|
|
201
|
-
for r in rows:
|
|
202
|
-
date = r[0][:16] if r[0] else '?'
|
|
203
|
-
req = (r[1] or '')[:120]
|
|
204
|
-
learned = (r[2] or '')[:100]
|
|
205
|
-
print(f'- [{date}] {req}')
|
|
206
|
-
if learned:
|
|
207
|
-
print(f' -> {learned}')
|
|
208
|
-
except Exception as e:
|
|
209
|
-
pass
|
|
210
|
-
" 2>/dev/null)
|
|
211
|
-
|
|
212
|
-
if [ -n "$RECENT_SESSIONS" ]; then
|
|
213
|
-
echo "$RECENT_SESSIONS" >> "$BRIEFING_FILE"
|
|
214
|
-
fi
|
|
215
|
-
fi
|
|
216
|
-
|
|
217
|
-
# ─── Cortex Report: what happened while user was away ───
|
|
218
|
-
# Check brain/ (canonical) first, fall back to cortex/ (legacy)
|
|
219
|
-
CORTEX_BRIEFING="$NEXO_HOME/brain/last-briefing.json"
|
|
220
|
-
if [ ! -f "$CORTEX_BRIEFING" ] && [ -f "$NEXO_HOME/cortex/last-briefing.json" ]; then
|
|
221
|
-
CORTEX_BRIEFING="$NEXO_HOME/cortex/last-briefing.json"
|
|
222
|
-
fi
|
|
223
|
-
if [ -f "$CORTEX_BRIEFING" ]; then
|
|
224
|
-
CORTEX_SECTION=$(python3 -c "
|
|
225
|
-
import json
|
|
226
|
-
try:
|
|
227
|
-
data = json.load(open('$CORTEX_BRIEFING'))
|
|
228
|
-
ts = data.get('timestamp', '?')
|
|
229
|
-
actions = data.get('actions_taken', [])
|
|
230
|
-
signals = data.get('signals_active', [])
|
|
231
|
-
recommendations = data.get('recommendations', [])
|
|
232
|
-
pending_q = data.get('pending_questions_unanswered', [])
|
|
233
|
-
dmn_summary = data.get('dmn_summary', '')
|
|
234
|
-
|
|
235
|
-
print()
|
|
236
|
-
print('## Cortex Report (last update: ' + str(ts)[:16] + ')')
|
|
237
|
-
if actions:
|
|
238
|
-
print('### Actions Executed')
|
|
239
|
-
for a in actions[-10:]:
|
|
240
|
-
if isinstance(a, dict):
|
|
241
|
-
print(f'- [{a.get(\"type\",\"?\")}] {a.get(\"detail\",\"\")}')
|
|
242
|
-
else:
|
|
243
|
-
print(f'- {a}')
|
|
244
|
-
if signals:
|
|
245
|
-
print('### Active Signals')
|
|
246
|
-
for s in signals[:5]:
|
|
247
|
-
print(f'- {s}')
|
|
248
|
-
if recommendations:
|
|
249
|
-
print('### Recommendations')
|
|
250
|
-
for r in recommendations[:3]:
|
|
251
|
-
print(f'- {r}')
|
|
252
|
-
if pending_q:
|
|
253
|
-
print(f'### Unanswered Questions: {len(pending_q)}')
|
|
254
|
-
for q in pending_q[:3]:
|
|
255
|
-
if isinstance(q, dict):
|
|
256
|
-
print(f'- {q.get(\"question\",\"?\")}')
|
|
257
|
-
else:
|
|
258
|
-
print(f'- {q}')
|
|
259
|
-
if dmn_summary:
|
|
260
|
-
print(f'### Last DMN: {str(dmn_summary)[:200]}')
|
|
261
|
-
except Exception as e:
|
|
262
|
-
print(f'## Cortex Report: error reading briefing ({e})')
|
|
263
|
-
" 2>/dev/null)
|
|
264
|
-
|
|
265
|
-
if [ -n "$CORTEX_SECTION" ]; then
|
|
266
|
-
echo "$CORTEX_SECTION" >> "$BRIEFING_FILE"
|
|
267
|
-
fi
|
|
268
|
-
fi
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# NEXO Memory Stop Hook (v7 — BLOCKING post-mortem with trivial session detection)
|
|
3
|
-
#
|
|
4
|
-
# v5 bug: used "approve" + systemMessage — AI never processed post-mortem.
|
|
5
|
-
# v6 fix: uses "block" — but blocked ALL sessions including trivial ones.
|
|
6
|
-
# v7 fix: detects trivial sessions (<5 tool calls) and approves immediately.
|
|
7
|
-
# Non-trivial sessions get blocked until post-mortem is done.
|
|
8
|
-
#
|
|
9
|
-
# Flow:
|
|
10
|
-
# Trivial session (quick question, <5 tool calls):
|
|
11
|
-
# → APPROVE immediately, no post-mortem needed
|
|
12
|
-
#
|
|
13
|
-
# Non-trivial session:
|
|
14
|
-
# 1. User closes → hook checks flag → not found → BLOCK
|
|
15
|
-
# 2. AI executes post-mortem → creates flag
|
|
16
|
-
# 3. User closes again → hook sees flag → APPROVE
|
|
17
|
-
set -uo pipefail
|
|
18
|
-
|
|
19
|
-
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
20
|
-
FLAG_FILE="$NEXO_HOME/operations/.postmortem-complete"
|
|
21
|
-
TODAY=$(date +%Y-%m-%d)
|
|
22
|
-
TOOL_LOG="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
|
|
23
|
-
|
|
24
|
-
# 0. Refresh diary draft with latest changes/decisions (best-effort)
|
|
25
|
-
python3 -c "
|
|
26
|
-
import sys, json, os
|
|
27
|
-
nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
|
|
28
|
-
nexo_code = os.environ.get('NEXO_CODE', nexo_home)
|
|
29
|
-
sys.path.insert(0, nexo_code)
|
|
30
|
-
os.environ['NEXO_SKIP_FS_INDEX'] = '1'
|
|
31
|
-
from db import init_db, get_db, get_active_sessions, upsert_diary_draft, get_diary_draft
|
|
32
|
-
init_db()
|
|
33
|
-
conn = get_db()
|
|
34
|
-
sessions = get_active_sessions()
|
|
35
|
-
for s in sessions:
|
|
36
|
-
sid = s['sid']
|
|
37
|
-
draft = get_diary_draft(sid)
|
|
38
|
-
if not draft:
|
|
39
|
-
continue
|
|
40
|
-
change_ids = [r[0] for r in conn.execute('SELECT id FROM change_log WHERE session_id = ?', (sid,)).fetchall()]
|
|
41
|
-
decision_ids = [r[0] for r in conn.execute('SELECT id FROM decisions WHERE session_id = ?', (sid,)).fetchall()]
|
|
42
|
-
upsert_diary_draft(
|
|
43
|
-
sid=sid,
|
|
44
|
-
tasks_seen=draft['tasks_seen'],
|
|
45
|
-
change_ids=json.dumps(change_ids),
|
|
46
|
-
decision_ids=json.dumps(decision_ids),
|
|
47
|
-
last_context_hint=draft['last_context_hint'],
|
|
48
|
-
heartbeat_count=draft['heartbeat_count'],
|
|
49
|
-
summary_draft=draft['summary_draft'],
|
|
50
|
-
)
|
|
51
|
-
" 2>/dev/null || true
|
|
52
|
-
|
|
53
|
-
# 1. Detect trivial session — count meaningful tool calls from THIS session only
|
|
54
|
-
# Uses .session-start-ts written by SessionStart hook
|
|
55
|
-
# A session with <5 tool calls (excluding Read/Grep/Glob/Bash) is trivial
|
|
56
|
-
SESSION_START_TS="$NEXO_HOME/operations/.session-start-ts"
|
|
57
|
-
|
|
58
|
-
# 0.5. Detect non-interactive (claude -p) sessions — skip post-mortem entirely
|
|
59
|
-
# SessionStart hook writes .session-start-ts. If missing or stale (>30 min),
|
|
60
|
-
# this is likely a -p script session — approve immediately.
|
|
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
|
|
63
|
-
cat << 'HOOKEOF'
|
|
64
|
-
{
|
|
65
|
-
"decision": "approve"
|
|
66
|
-
}
|
|
67
|
-
HOOKEOF
|
|
68
|
-
exit 0
|
|
69
|
-
fi
|
|
70
|
-
SESSION_START=0
|
|
71
|
-
if [ -f "$SESSION_START_TS" ]; then
|
|
72
|
-
SESSION_START=$(cat "$SESSION_START_TS" 2>/dev/null || echo "0")
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
TOOL_COUNT=0
|
|
76
|
-
if [ -f "$TOOL_LOG" ]; then
|
|
77
|
-
TOOL_COUNT=$(python3 -c "
|
|
78
|
-
import json, sys, os
|
|
79
|
-
session_start = float(os.environ.get('SESSION_START', '0'))
|
|
80
|
-
count = 0
|
|
81
|
-
for line in open('$TOOL_LOG'):
|
|
82
|
-
try:
|
|
83
|
-
d = json.loads(line)
|
|
84
|
-
# Only count tools from THIS session (after session-start-ts)
|
|
85
|
-
ts = d.get('timestamp', '')
|
|
86
|
-
if ts and session_start > 0:
|
|
87
|
-
from datetime import datetime
|
|
88
|
-
try:
|
|
89
|
-
entry_ts = datetime.fromisoformat(ts.replace('Z', '+00:00')).timestamp()
|
|
90
|
-
if entry_ts < session_start:
|
|
91
|
-
continue
|
|
92
|
-
except:
|
|
93
|
-
pass
|
|
94
|
-
t = d.get('tool_name', '')
|
|
95
|
-
if t and t not in ('Read', 'Grep', 'Glob', 'Bash', 'ToolSearch'):
|
|
96
|
-
count += 1
|
|
97
|
-
except:
|
|
98
|
-
pass
|
|
99
|
-
print(count)
|
|
100
|
-
" 2>/dev/null || echo "0")
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
# Trivial session → approve immediately, no buffer writing, skip post-mortem
|
|
104
|
-
if [ "$TOOL_COUNT" -lt 5 ]; then
|
|
105
|
-
cat << 'HOOKEOF'
|
|
106
|
-
{
|
|
107
|
-
"decision": "approve"
|
|
108
|
-
}
|
|
109
|
-
HOOKEOF
|
|
110
|
-
exit 0
|
|
111
|
-
fi
|
|
112
|
-
|
|
113
|
-
# 2. Non-trivial session — check if post-mortem was already completed
|
|
114
|
-
# Flag has NO TTL — it persists until SessionStart cleans it up next session.
|
|
115
|
-
POSTMORTEM_DONE=false
|
|
116
|
-
if [ -f "$FLAG_FILE" ]; then
|
|
117
|
-
POSTMORTEM_DONE=true
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
if [ "$POSTMORTEM_DONE" = true ]; then
|
|
121
|
-
# Post-mortem was done — allow session to close
|
|
122
|
-
# NOTE: do NOT delete flag here — SessionStart cleans it up next session
|
|
123
|
-
cat << 'HOOKEOF'
|
|
124
|
-
{
|
|
125
|
-
"decision": "approve"
|
|
126
|
-
}
|
|
127
|
-
HOOKEOF
|
|
128
|
-
else
|
|
129
|
-
# Post-mortem NOT done — BLOCK session close and inject instructions
|
|
130
|
-
cat << 'HOOKEOF'
|
|
131
|
-
{
|
|
132
|
-
"decision": "block",
|
|
133
|
-
"reason": "STOP HOOK — MANDATORY POST-MORTEM before ending (do NOT ask permission, do NOT skip):\n\n## 1. SELF-CRITIQUE (MANDATORY — write to session diary)\nAnswer these questions in the self_critique field of nexo_session_diary_write:\n- Did the user have to ask me for something I should have detected or done on my own?\n- Did I wait for the user to tell me something I could have verified proactively?\n- Are there systems/states I can check next session without being asked?\n- Did I repeat an error that already had a registered learning?\n- What would I do differently if I repeated this session?\nIf any answer is YES — write the specific rule that would prevent repetition.\nIf the session was flawless, write 'No self-critique — clean session.'\n\n## 2. SESSION BUFFER\nIf the session was NOT trivial, append ONE JSON line to $NEXO_HOME/brain/session_buffer.jsonl:\n{\"ts\":\"YYYY-MM-DDTHH:MM:SS\",\"tasks\":[...],\"decisions\":[...],\"user_patterns\":[...],\"files_modified\":[...],\"errors_resolved\":[...],\"self_critique\":\"short summary\",\"mood\":\"focused|impatient|exploratory|frustrated|satisfied|neutral\",\"source\":\"claude\"}\n\n## 3. FOLLOWUPS\nIf there were deploys/cron changes/fixes — nexo_followup_create with verification date.\n\n## 4. PROACTIVE SEEDS\nWhat can I leave prepared so the next session starts doing useful work without the user asking?\n\n## 5. MARK COMPLETE\nWhen ALL of the above is done, run:\nbash -c 'mkdir -p $NEXO_HOME/operations && date +%s > $NEXO_HOME/operations/.postmortem-complete'\nThe user will close again and the hook will approve.\n\nIMPORTANT: Do NOT say goodbye, do NOT say goodnight or any farewell. Just execute the steps and mark complete."
|
|
134
|
-
}
|
|
135
|
-
HOOKEOF
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
# 3. Session buffer fallback REMOVED (v8)
|
|
139
|
-
# The old hook-fallback was 86% noise. Session diary (written by Claude during
|
|
140
|
-
# post-mortem) is the only source of truth now. No more buffer writing.
|