diff --git a/bin/tower-watchdog.sh b/bin/tower-watchdog.sh index da622e8..1e1182b 100755 --- a/bin/tower-watchdog.sh +++ b/bin/tower-watchdog.sh @@ -1,8 +1,13 @@ #!/usr/bin/env bash # ── Tower Watchdog ───────────────────────────────────────────────────── -# Ensures the tower session stays alive. Restarts dead panes. +# Ensures the tower session stays alive. Restarts dead panes/windows. # Run via cron: */5 * * * * ~/hermes-config/bin/tower-watchdog.sh # +# Layout: +# Window 1, Pane 1: tower-hermes.sh (conversation driver) +# Window 1, Pane 2: tower-status.sh (status dashboard) +# Window 2: tower-timmy.sh (Timmy loop, hidden) +# # Source-controlled: gitea/rockachopa/hermes-config # ─────────────────────────────────────────────────────────────────────── @@ -17,49 +22,67 @@ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; } # If session doesn't exist at all, recreate it if ! tmux has-session -t "$SESSION" 2>/dev/null; then log "Session '$SESSION' missing. Recreating." - # Start detached — don't attach (we're in cron) tmux new-session -d -s "$SESSION" -n "tower" -x 200 -y 50 - tmux split-window -h -t "$SESSION:1.1" - tmux select-pane -t "$SESSION:1.1" -T "⚡ Hermes" - tmux select-pane -t "$SESSION:1.2" -T "🕐 Timmy" - tmux select-layout -t "$SESSION:1" even-horizontal + tmux split-window -h -p 35 -t "$SESSION:1.1" + tmux select-pane -t "$SESSION:1.1" -T "⚡ Tower" + tmux select-pane -t "$SESSION:1.2" -T "📊 Status" tmux send-keys -t "$SESSION:1.1" "$TOWER_BIN/tower-hermes.sh" Enter - tmux send-keys -t "$SESSION:1.2" "$TOWER_BIN/tower-timmy.sh" Enter - log "Session recreated with both panes." + tmux send-keys -t "$SESSION:1.2" "$TOWER_BIN/tower-status.sh" Enter + # Hidden window for Timmy + tmux new-window -t "$SESSION" -n "timmy-bg" + tmux send-keys -t "$SESSION:2" "$TOWER_BIN/tower-timmy.sh" Enter + tmux select-window -t "$SESSION:1" + log "Session recreated (conversation + status + timmy-bg)." exit 0 fi -# Session exists — check each pane +# Session exists — check window 1 panes PANE_COUNT=$(tmux list-panes -t "$SESSION:1" 2>/dev/null | wc -l | tr -d ' ') if [ "$PANE_COUNT" -lt 2 ]; then - log "Only $PANE_COUNT pane(s). Killing and recreating session." + log "Window 1 has only $PANE_COUNT pane(s). Killing and recreating session." tmux kill-session -t "$SESSION" 2>/dev/null exec "$0" # re-run to recreate fi -# Check if the loops are actually running in each pane -for PANE in 1 2; do - PANE_PID=$(tmux display-message -p -t "$SESSION:1.$PANE" '#{pane_pid}' 2>/dev/null) - if [ -z "$PANE_PID" ]; then - continue - fi - - # Check if there's a running process (not just a shell prompt) - CHILDREN=$(pgrep -P "$PANE_PID" 2>/dev/null | wc -l | tr -d ' ') +# Check Hermes loop (window 1, pane 1) +HERMES_PID=$(tmux display-message -p -t "$SESSION:1.1" '#{pane_pid}' 2>/dev/null) +if [ -n "$HERMES_PID" ]; then + CHILDREN=$(pgrep -P "$HERMES_PID" 2>/dev/null | wc -l | tr -d ' ') if [ "$CHILDREN" -eq 0 ]; then - if [ "$PANE" -eq 1 ]; then - log "Hermes pane idle. Restarting tower-hermes.sh" - # Clean stale lock - rm -f "$HOME/.tower/hermes.lock" - tmux send-keys -t "$SESSION:1.1" "$TOWER_BIN/tower-hermes.sh" Enter - else + log "Hermes pane idle. Restarting tower-hermes.sh" + rm -f "$HOME/.tower/hermes.lock" + tmux send-keys -t "$SESSION:1.1" "$TOWER_BIN/tower-hermes.sh" Enter + fi +fi + +# Check status pane (window 1, pane 2) — restart if dead +STATUS_PID=$(tmux display-message -p -t "$SESSION:1.2" '#{pane_pid}' 2>/dev/null) +if [ -n "$STATUS_PID" ]; then + CHILDREN=$(pgrep -P "$STATUS_PID" 2>/dev/null | wc -l | tr -d ' ') + if [ "$CHILDREN" -eq 0 ]; then + log "Status pane idle. Restarting tower-status.sh" + tmux send-keys -t "$SESSION:1.2" "$TOWER_BIN/tower-status.sh" Enter + fi +fi + +# Check Timmy loop (window 2) +if ! tmux has-window -t "$SESSION:2" 2>/dev/null; then + log "Timmy window missing. Recreating." + tmux new-window -t "$SESSION" -n "timmy-bg" + tmux send-keys -t "$SESSION:2" "$TOWER_BIN/tower-timmy.sh" Enter + tmux select-window -t "$SESSION:1" +else + TIMMY_PID=$(tmux display-message -p -t "$SESSION:2" '#{pane_pid}' 2>/dev/null) + if [ -n "$TIMMY_PID" ]; then + CHILDREN=$(pgrep -P "$TIMMY_PID" 2>/dev/null | wc -l | tr -d ' ') + if [ "$CHILDREN" -eq 0 ]; then log "Timmy pane idle. Restarting tower-timmy.sh" rm -f "$HOME/.tower/timmy.lock" - tmux send-keys -t "$SESSION:1.2" "$TOWER_BIN/tower-timmy.sh" Enter + tmux send-keys -t "$SESSION:2" "$TOWER_BIN/tower-timmy.sh" Enter fi fi -done +fi # Trim log if > 1000 lines if [ -f "$LOG" ] && [ "$(wc -l < "$LOG")" -gt 1000 ]; then diff --git a/tmux/tower-session.sh b/tmux/tower-session.sh index c26e4e1..9e0d2a5 100755 --- a/tmux/tower-session.sh +++ b/tmux/tower-session.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # ── Tower Session ────────────────────────────────────────────────────── -# Two-pane tmux session: Hermes ↔ Timmy conversation loop +# Two-pane tmux session: Hermes ↔ Timmy conversation + status dashboard # -# Left pane: Hermes (cloud, Claude) talking TO Timmy -# Right pane: Timmy (local/Anthropic) talking TO Hermes +# Left pane (65%): Hermes loop — drives the conversation, shows both sides +# Right pane (35%): Status dashboard — loop health, message stats, infra # # Communication: file-based message passing via ~/.tower/ # Self-healing: watchdog checks both panes, restarts if dead @@ -27,26 +27,32 @@ if tmux has-session -t "$SESSION" 2>/dev/null; then fi # ── Create session with two panes ───────────────────────────────────── -# Left pane: Hermes side of the conversation +# Left pane: Hermes conversation loop (drives the conversation, shows both sides) tmux new-session -d -s "$SESSION" -n "tower" -x 200 -y 50 -# Right pane: Timmy side -tmux split-window -h -t "$SESSION:1.1" +# Right pane: Status dashboard (35% width) +tmux split-window -h -p 35 -t "$SESSION:1.1" # Set pane titles -tmux select-pane -t "$SESSION:1.1" -T "⚡ Hermes" -tmux select-pane -t "$SESSION:1.2" -T "🕐 Timmy" +tmux select-pane -t "$SESSION:1.1" -T "⚡ Tower" +tmux select-pane -t "$SESSION:1.2" -T "📊 Status" -# Equal width -tmux select-layout -t "$SESSION:1" even-horizontal - -# ── Start the conversation loops ────────────────────────────────────── +# ── Start the loops ────────────────────────────────────────────────── +# Pane 1: Hermes loop (conversation driver — shows Hermes & Timmy messages) tmux send-keys -t "$SESSION:1.1" \ "$TOWER_BIN/tower-hermes.sh" Enter + +# Pane 2: Status dashboard tmux send-keys -t "$SESSION:1.2" \ + "$TOWER_BIN/tower-status.sh" Enter + +# Hidden window 2: Timmy loop (no need to watch — status pane monitors health) +tmux new-window -t "$SESSION" -n "timmy-bg" +tmux send-keys -t "$SESSION:2" \ "$TOWER_BIN/tower-timmy.sh" Enter -# Focus left pane (Hermes) +# Back to window 1, focus conversation pane +tmux select-window -t "$SESSION:1" tmux select-pane -t "$SESSION:1.1" # ── Attach ────────────────────────────────────────────────────────────