#!/bin/bash # Verifica che ogni sentinella persistente sia viva; se non lo e' (o non e' mai stata lanciata), # la (ri)lancia. Ogni sottocartella e' una sentinella a se', identificata dal proprio CLAUDE.md # (ruolo statico, caricato automaticamente da Claude Code in base alla cwd al lancio) - lo stesso # script funziona per qualunque sentinella futura, non solo inbox-documenti. Vedi # archivio/Adrian/riferimento/claude-cli-headless.md. # # Scansiona due gruppi di cartelle (11/09/2026, esteso quando expert-domotica si e' spostata # fuori da scripts/slave-sentinels/ dentro gemello-nave/ - Nave-esclusivo, mentre expert-data/ # expert-flussi restano qui in sospeso, vedi project_gemello_nave.md): SENTINELS_DIR (il gruppo # storico, sweep via glob su tutte le sottocartelle) + EXTRA_SENTINELS (elenco esplicito di # singole cartelle fuori da quel gruppo - gemello-nave/expert-domotica e, dall'11/09/2026, # gemello-nave/ stessa: migrata da cold-start -p a sentinella --bg sempre viva, vedi # project_gemello_nave_bg.md - a differenza degli esperti, per lei conviene rilanciarla appena # possibile se muore, non lasciarla scaduta finche' non serve davvero). # # Da eseguire a ogni avvio sessione (passo 7 di avvio-sessione) - va bene anche a mano. # # Uso: slave_sentinel_ensure.sh [nome-cartella] # Senza argomenti: sweep completo su tutte le sottocartelle (comportamento originale). # Con un argomento: controlla/rilancia solo quella sottocartella - usato da # servizio-madre-watch (04/09/2026) per un tick periodico mirato solo su # sentinella-gmail-personale (l'unica istanza che deve girare in continuo, non on-demand), # senza rilanciare preventivamente anche gli esperti che invece vogliamo lasciare scadere # e rilanciare a freddo solo quando servono davvero (vedi memory/MEMORY.md). set -euo pipefail cd /mnt/ssd/data/Dropbox/adrian export CLAUDE_CODE_OAUTH_TOKEN="$(grep '^CLAUDE_CODE_OAUTH_TOKEN=' archivio/_secrets/credentials.md | cut -d= -f2-)" unset VSCODE_IPC_HOOK_CLI || true SENTINELS_DIR="scripts/slave-sentinels" EXTRA_SENTINELS=("gemello-nave/expert-domotica/" "gemello-nave/") BOOTSTRAP="Sei la sentinella descritta nel CLAUDE.md di questa cartella di lavoro. Seguilo alla lettera, a partire da ora." ONLY="${1:-}" shopt -s nullglob all_dirs=("$SENTINELS_DIR"/*/ "${EXTRA_SENTINELS[@]}") for dir in "${all_dirs[@]}"; do name="$(basename "$dir")" if [ -n "$ONLY" ] && [ "$name" != "$ONLY" ]; then continue fi id_file="${dir}.session_id" # Convenzione di nomenclatura (30/08/2026): il nome del processo --name esprime la gerarchia # classe-nome (es. "expert-domotica"). Cartelle nuove portano gia' il prefisso di classe nel # proprio nome ("expert-", "sentinella-") e vengono lanciate col nome della cartella cosi' # com'e'. Le due cartelle storiche (gate-ufficio, inbox-documenti) non hanno il prefisso nel # nome della cartella - mantenuto il vecchio comportamento (prefisso "sentinella-" aggiunto qui) # per non doverle rinominare/rilanciare solo per uniformita' cosmetica. case "$name" in expert-*|sentinella-*|gemello-nave) proc_name="$name" ;; *) proc_name="sentinella-$name" ;; esac alive="" state="mai-lanciata" if [ -f "$id_file" ]; then sid="$(cat "$id_file")" # Il vero indicatore di "processo ancora vivo" e' la presenza del campo "pid" nel record - # "state" invece riflette solo l'ultimo turno completato (es. "done" quando non c'e' nessuna # tool call attiva come un Monitor, "working" quando c'e') e vale anche per un processo # perfettamente vivo ma a riposo tra una domanda e l'altra - un esperto senza Monitor ci passa # la maggior parte del tempo. Bug reale trovato il 30/08/2026: una prima versione di questo # controllo si basava su "state" ("done" trattato come morto) e ha causato un rilancio inutile # (processo duplicato) di domotica-expert mentre era solo idle, non morto. state=$(claude agents --json --all 2>/dev/null | python3 -c " import json, sys sid = '$sid' try: data = json.load(sys.stdin) except Exception: print('unknown'); sys.exit() for s in data: if s.get('id') == sid: print('alive' if 'pid' in s else 'exited') sys.exit() print('missing') ") if [ "$state" = "alive" ]; then alive="yes" fi fi if [ "$alive" = "yes" ]; then echo "[$name] gia' viva (id $sid, stato $state)" # Controllo meccanico di vivacita' reale (non solo processo vivo): se la sentinella tiene un # last_check.txt (timestamp ISO dell'ultimo giro completato), un file troppo vecchio segnala # che il processo e' vivo ma bloccato/rotto - "pid presente" da solo non lo rileverebbe. # Soglia 60 min = 3x il tick di sentinella-gmail-personale (20 min), generica per design. lc_file="${dir}last_check.txt" if [ -f "$lc_file" ]; then lc_epoch=$(date -d "$(cat "$lc_file")" +%s 2>/dev/null || echo 0) now_epoch=$(date +%s) age_min=$(( (now_epoch - lc_epoch) / 60 )) if [ "$lc_epoch" -eq 0 ] || [ "$age_min" -gt 60 ]; then echo "[$name] ATTENZIONE: last_check.txt fermo da ${age_min} minuti (o illeggibile) - processo vivo ma probabilmente bloccato" fi fi else echo "[$name] non attiva (stato: $state) - rilancio" out=$(cd "$dir" && claude --bg --name "$proc_name" "$BOOTSTRAP" 2>&1) new_id=$(echo "$out" | grep -oP 'backgrounded ยท \K[a-f0-9]+' || true) if [ -n "$new_id" ]; then echo "$new_id" > "$id_file" echo "[$name] rilanciata, nuovo id $new_id" else echo "[$name] ATTENZIONE: rilancio tentato ma id non riconosciuto nell'output:" echo "$out" fi fi done