Two-pane tmux session with file-based message passing: - tower-hermes.sh: Hermes side (cloud/Claude) - tower-timmy.sh: Timmy side (HERMES_HOME=~/.timmy) - tower-watchdog.sh: self-healing, restarts dead panes - tower-session.sh: tmux bootstrap script Communication via ~/.tower/*.msg files. Both agents maintain named sessions (tower-hermes, tower-timmy) for conversation continuity across restarts.
69 lines
2.7 KiB
Bash
Executable File
69 lines
2.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ── Tower Watchdog ─────────────────────────────────────────────────────
|
|
# Ensures the tower session stays alive. Restarts dead panes.
|
|
# Run via cron: */5 * * * * ~/hermes-config/bin/tower-watchdog.sh
|
|
#
|
|
# Source-controlled: gitea/rockachopa/hermes-config
|
|
# ───────────────────────────────────────────────────────────────────────
|
|
|
|
SESSION="tower"
|
|
TOWER_BIN="$HOME/hermes-config/bin"
|
|
LOG="$HOME/.tower/watchdog.log"
|
|
|
|
mkdir -p "$HOME/.tower"
|
|
|
|
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 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."
|
|
exit 0
|
|
fi
|
|
|
|
# Session exists — check each pane
|
|
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."
|
|
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 ' ')
|
|
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 "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
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Trim log if > 1000 lines
|
|
if [ -f "$LOG" ] && [ "$(wc -l < "$LOG")" -gt 1000 ]; then
|
|
tail -500 "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG"
|
|
log "Log trimmed to 500 lines."
|
|
fi
|