Files
brain-template/scripts/brain-watch-local.sh
Tetardtek 878886cd51 feat: brain-template v2.0 — BSI-v3 complet + tiers documentés
- README reécrit : tiers free/pro/full + modèle clé API + multi-instance
- Sync agents/ (57 agents, kernel-isolation validated)
- Sync scripts/ BSI-v3 (file-lock, preflight, human-gate, brain-status)
- KERNEL.md v0.7.0 — zones + délégation + rendering + isolation
- brain-compose.yml v0.7.0 — rendering mode + kerneluser
- workflows/ — template + brain-engine exemple
- locks/.gitkeep + claims/.gitkeep
- helloWorld : RAG boot tier full only (bsi-rag retiré du template)
2026-03-16 23:26:38 +01:00

215 lines
8.2 KiB
Bash
Executable File

#!/bin/bash
# brain-watch-local.sh — Daemon crash handler + supervisor local
# Extension système HORS brain — zéro token, zéro Claude.
#
# Responsabilités :
# 1. Crash detection : process Claude mort → auto-close claim BSI
# 2. Stale TTL check : claim expiré → alerte desktop + Telegram
# 3. Réaction aux changements BRAIN-INDEX.md via inotify (ou poll fallback)
# 4. Notify : notify-send (desktop) + brain-notify.sh (Telegram)
#
# PID tracking — convention helloWorld :
# Ouverture claim : echo $PPID > ~/.claude/sessions/<sess-id>.pid
# Fermeture claim : rm -f ~/.claude/sessions/<sess-id>.pid
#
# Install :
# scripts/install-brain-watch.sh local
# systemctl --user enable --now brain-watch-local
set -euo pipefail
BRAIN_ROOT="${BRAIN_ROOT:-$HOME/Dev/Brain}"
BRAIN_INDEX="$BRAIN_ROOT/BRAIN-INDEX.md"
BRAIN_NOTIFY="$BRAIN_ROOT/scripts/brain-notify.sh"
BSI_QUERY="$BRAIN_ROOT/scripts/bsi-query.sh"
SESSIONS_DIR="${HOME}/.claude/sessions"
STALE_NOTIFIED_FILE="/tmp/brain-watch-local-stale.txt"
POLL_INTERVAL=30
LOG_PREFIX="[brain-watch-local]"
mkdir -p "$SESSIONS_DIR"
touch "$STALE_NOTIFIED_FILE"
# ── Helpers ───────────────────────────────────────────────────────────────────
log() { echo "$LOG_PREFIX $*"; }
notify_desktop() {
local msg="$1"
command -v notify-send &>/dev/null \
&& notify-send "🧠 Brain SUPERVISOR" "$msg" -u normal -t 8000 \
|| true
}
notify_telegram() {
local msg="$1" level="${2:-info}"
[[ -x "$BRAIN_NOTIFY" ]] && "$BRAIN_NOTIFY" "$msg" "$level" || true
}
notify_all() {
notify_desktop "$1"
notify_telegram "$1" "${2:-info}"
}
# ── Crash detection ───────────────────────────────────────────────────────────
check_crashed_sessions() {
for pid_file in "$SESSIONS_DIR"/*.pid; do
[[ -f "$pid_file" ]] || continue
local sess_id pid claim_line claim_state
sess_id=$(basename "$pid_file" .pid)
pid=$(cat "$pid_file" 2>/dev/null | tr -d '[:space:]' || echo "")
[[ -z "$pid" ]] && continue
# Process encore vivant → skip
kill -0 "$pid" 2>/dev/null && continue
# Process mort — claim encore open ?
claim_line=$(grep "^| ${sess_id} " "$BRAIN_INDEX" 2>/dev/null | head -1 || true)
[[ -z "$claim_line" ]] && { rm -f "$pid_file"; continue; }
claim_state=$(echo "$claim_line" | awk -F'|' '{print $8}' | xargs 2>/dev/null || echo "")
if [[ "$claim_state" == "open" ]]; then
log "CRASH : $sess_id (PID $pid mort, claim open) → auto-close"
notify_all "💥 Session crashée : $sess_id\nClaim auto-fermé par le crash handler." "urgent"
_auto_close_claim "$sess_id"
fi
rm -f "$pid_file"
done
}
_auto_close_claim() {
local sess_id="$1"
# Remplacer | open | par | closed | sur la ligne du claim
sed -i "s/^| ${sess_id} \(.*\)| open |/| ${sess_id} \1| closed |/" "$BRAIN_INDEX" || {
log "WARNING : sed failed sur $sess_id"
return 1
}
cd "$BRAIN_ROOT"
git add BRAIN-INDEX.md \
&& git commit -m "bsi: auto-close crashed claim ${sess_id}" \
&& git push \
&& log "$sess_id fermé + pushé" \
|| log "WARNING : commit/push échoué après auto-close $sess_id"
}
# ── Stale TTL ─────────────────────────────────────────────────────────────────
check_stale_claims() {
# Source : brain.db via bsi-query.sh — fallback grep BRAIN-INDEX si brain.db absent
local stale_lines
if [[ -x "$BSI_QUERY" ]] && bash "$BSI_QUERY" count-stale &>/dev/null; then
stale_lines=$(bash "$BSI_QUERY" stale 2>/dev/null || true)
else
# Fallback : parse BRAIN-INDEX.md (brain.db absent ou bsi-query.sh indisponible)
stale_lines=$(grep '^| sess-' "$BRAIN_INDEX" 2>/dev/null | grep '| open |' || true)
[[ -z "$stale_lines" ]] && return
# Format fallback : convertir ligne markdown en format bsi-query (sess_id | scope | opened_at | age_h)
stale_lines=$(echo "$stale_lines" | awk -F'|' '{
gsub(/^ +| +$/,"",$2); gsub(/^ +| +$/,"",$4); gsub(/^ +| +$/,"",$6);
print $2 " | " $4 " | " $6 " | fallback"
}')
fi
[[ -z "$stale_lines" ]] && return
while IFS= read -r line; do
[[ -z "$line" ]] && continue
local sess_id
sess_id=$(echo "$line" | cut -d'|' -f1 | xargs)
[[ -z "$sess_id" ]] && continue
grep -qF "$sess_id" "$STALE_NOTIFIED_FILE" 2>/dev/null && continue
local age_h
age_h=$(echo "$line" | cut -d'|' -f4 | xargs)
log "STALE : $sess_id (${age_h})"
notify_all "⚠️ Claim stale : $sess_id\n${age_h}\nRecovery requis." "update"
echo "$sess_id" >> "$STALE_NOTIFIED_FILE"
done <<< "$stale_lines"
}
# ── BSI events (nouveau claim / fermé / signals) ──────────────────────────────
PREV_HASH=""
PREV_CLAIMS=0
bsi_events() {
local new_hash new_claims
new_hash=$(md5sum "$BRAIN_INDEX" | cut -d' ' -f1)
[[ "$new_hash" == "$PREV_HASH" ]] && return
PREV_HASH="$new_hash"
# Source : brain.db via bsi-query.sh — fallback grep BRAIN-INDEX si brain.db absent
if [[ -x "$BSI_QUERY" ]] && bash "$BSI_QUERY" count-open &>/dev/null; then
new_claims=$(bash "$BSI_QUERY" count-open 2>/dev/null || echo 0)
else
new_claims=$(grep '^| sess-' "$BRAIN_INDEX" 2>/dev/null | grep -c '| open |' || echo 0)
fi
if [[ "$new_claims" -gt "$PREV_CLAIMS" ]]; then
local sess
if [[ -x "$BSI_QUERY" ]] && bash "$BSI_QUERY" count-open &>/dev/null; then
sess=$(bash "$BSI_QUERY" open 2>/dev/null | head -1 | cut -d'|' -f1 | xargs)
else
sess=$(grep '^| sess-' "$BRAIN_INDEX" | grep '| open |' | tail -1 | awk -F'|' '{print $2}' | xargs)
fi
log "Nouveau claim : $sess"
notify_all "🟢 Nouvelle session : $sess" "update"
fi
if [[ "$new_claims" -lt "$PREV_CLAIMS" ]]; then
log "Claim fermé — restants : $new_claims"
notify_all "✅ Session fermée — claims actifs : $new_claims" "info"
fi
PREV_CLAIMS="$new_claims"
# BLOCKED_ON — uniquement sur lignes sig-
local blocked
blocked=$(grep '^| sig-' "$BRAIN_INDEX" 2>/dev/null | grep 'BLOCKED_ON' | head -1 || true)
if [[ -n "$blocked" ]]; then
log "ESCALADE : BLOCKED_ON"
notify_all "🚨 Conflit inter-sessions\n$blocked\nIntervention requise." "urgent"
fi
# CHECKPOINT / HANDOFF pending
local signal
signal=$(grep '^| sig-' "$BRAIN_INDEX" 2>/dev/null | grep -E 'CHECKPOINT|HANDOFF' | grep 'pending' | head -1 || true)
if [[ -n "$signal" ]]; then
local sig_type sig_to
sig_type=$(echo "$signal" | awk -F'|' '{print $5}' | xargs)
sig_to=$(echo "$signal" | awk -F'|' '{print $4}' | xargs)
log "SIGNAL : $sig_type$sig_to"
notify_all "📋 $sig_type$sig_to\nHandoff disponible." "update"
fi
}
# ── Boucle principale ─────────────────────────────────────────────────────────
log "Démarré — BRAIN_INDEX: $BRAIN_INDEX"
PREV_HASH=$(md5sum "$BRAIN_INDEX" 2>/dev/null | cut -d' ' -f1 || echo "")
PREV_CLAIMS=$(grep '^| sess-' "$BRAIN_INDEX" 2>/dev/null | grep -c '| open |' || echo 0)
if command -v inotifywait &>/dev/null; then
log "Mode inotify — réactif"
while true; do
inotifywait -q -t "$POLL_INTERVAL" -e close_write "$BRAIN_INDEX" 2>/dev/null || true
check_crashed_sessions
check_stale_claims
bsi_events
done
else
log "Mode poll ${POLL_INTERVAL}s (apt install inotify-tools pour le mode réactif)"
while true; do
sleep "$POLL_INTERVAL"
check_crashed_sessions
check_stale_claims
bsi_events
done
fi