From c4210ece4be94de5ab8999baeeecb7527cb1fb44 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sun, 15 Mar 2026 13:45:41 -0400 Subject: [PATCH] feat: kimi independence + system watchdog + webhook listener - Kimi user activated, token created, write access granted - Watchdog now monitors: timmy-loop, webhook (port 7777), gitea, ollama - Webhook listener for instant replies on rockachopa comments - Master startup script (launchd) brings up full system on reboot --- bin/hermes-startup.sh | 94 +++++++++++++++++++++++++++++++++++++++++++ bin/timmy-watchdog.sh | 86 ++++++++++++++++++++++++++++++--------- cron/jobs.json | 43 +++++--------------- kimi_token | 1 + 4 files changed, 171 insertions(+), 53 deletions(-) create mode 100755 bin/hermes-startup.sh create mode 100644 kimi_token diff --git a/bin/hermes-startup.sh b/bin/hermes-startup.sh new file mode 100755 index 0000000..6087c1e --- /dev/null +++ b/bin/hermes-startup.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# ── Hermes Master Startup ───────────────────────────────────────────── +# Brings up the entire system after a reboot. +# Called by launchd (ai.hermes.startup) or manually. +# +# Boot order: +# 1. Gitea (homebrew launchd — already handles itself) +# 2. Ollama (macOS app — already handles itself via login item) +# 3. Hermes Gateway (launchd — already handles itself) +# 4. Webhook listener (port 7777) +# 5. Timmy-loop tmux session (4-pane dashboard) +# 6. Hermes cron engine (runs inside gateway) +# +# This script ensures 4 and 5 are alive. 1-3 and 6 are handled by +# their own launchd plists / login items. +# ─────────────────────────────────────────────────────────────────────── + +set -euo pipefail +export PATH="/opt/homebrew/bin:$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH" + +LOG="$HOME/.hermes/logs/startup.log" +mkdir -p "$(dirname "$LOG")" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG" +} + +wait_for_port() { + local port=$1 name=$2 max=$3 + local i=0 + while ! lsof -ti:"$port" >/dev/null 2>&1; do + sleep 1 + i=$((i + 1)) + if [ "$i" -ge "$max" ]; then + log "WARN: $name not up on port $port after ${max}s" + return 1 + fi + done + log "OK: $name alive on port $port" + return 0 +} + +# ── Prerequisites ────────────────────────────────────────────────────── + +log "=== Hermes Master Startup ===" + +# Wait for Gitea (port 3000) — up to 30s +log "Waiting for Gitea..." +wait_for_port 3000 "Gitea" 30 + +# Wait for Ollama (port 11434) — up to 30s +log "Waiting for Ollama..." +wait_for_port 11434 "Ollama" 30 + +# ── Webhook Listener (port 7777) ─────────────────────────────────────── + +if lsof -ti:7777 >/dev/null 2>&1; then + log "OK: Webhook listener already running on port 7777" +else + log "Starting webhook listener..." + tmux has-session -t webhook 2>/dev/null && tmux kill-session -t webhook + tmux new-session -d -s webhook "python3 $HOME/.hermes/bin/gitea-webhook-listener.py" + sleep 2 + if lsof -ti:7777 >/dev/null 2>&1; then + log "OK: Webhook listener started on port 7777" + else + log "FAIL: Webhook listener did not start" + fi +fi + +# ── Timmy Loop (tmux session) ────────────────────────────────────────── + +STOP_FILE="$HOME/Timmy-Time-dashboard/.loop/STOP" + +if [ -f "$STOP_FILE" ]; then + log "SKIP: Timmy loop — STOP file present at $STOP_FILE" +elif tmux has-session -t timmy-loop 2>/dev/null; then + # Check if the loop pane is actually alive + PANE0_PID=$(tmux list-panes -t "timmy-loop:0.0" -F '#{pane_pid}' 2>/dev/null || true) + if [ -n "$PANE0_PID" ] && kill -0 "$PANE0_PID" 2>/dev/null; then + log "OK: Timmy loop session alive" + else + log "WARN: Timmy loop session exists but pane dead. Restarting..." + tmux kill-session -t timmy-loop 2>/dev/null + "$HOME/.hermes/bin/timmy-tmux.sh" + log "OK: Timmy loop restarted" + fi +else + log "Starting timmy-loop session..." + "$HOME/.hermes/bin/timmy-tmux.sh" + log "OK: Timmy loop started" +fi + +log "=== Startup complete ===" diff --git a/bin/timmy-watchdog.sh b/bin/timmy-watchdog.sh index 5c31e3d..cd61a0f 100755 --- a/bin/timmy-watchdog.sh +++ b/bin/timmy-watchdog.sh @@ -1,39 +1,85 @@ #!/usr/bin/env bash -# ── Timmy Loop Watchdog ──────────────────────────────────────────────── -# Checks if the timmy-loop tmux session is alive. Restarts if dead. -# Designed to run via cron every 5 minutes. +# ── Hermes System Watchdog ───────────────────────────────────────────── +# Monitors all critical services. Restarts anything that's dead. +# Runs via hermes cron every 8 minutes. +# +# Watches: +# 1. timmy-loop tmux session (4-pane dev dashboard) +# 2. Webhook listener on port 7777 (Gitea comment watcher) +# 3. Gitea on port 3000 (just reports, can't restart homebrew services) +# 4. Ollama on port 11434 (just reports) # ─────────────────────────────────────────────────────────────────────── -SESSION="timmy-loop" -LAUNCHER="$HOME/.hermes/bin/timmy-tmux.sh" +export PATH="/opt/homebrew/bin:$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH" + WATCHDOG_LOG="$HOME/Timmy-Time-dashboard/.loop/watchdog.log" +mkdir -p "$(dirname "$WATCHDOG_LOG")" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$WATCHDOG_LOG" } -# Check if tmux session exists -if tmux has-session -t "$SESSION" 2>/dev/null; then - # Session exists. Check if the loop pane (pane 0) is still running. +STATUS="ok" + +# ── 1. Timmy Loop ────────────────────────────────────────────────────── + +SESSION="timmy-loop" +LAUNCHER="$HOME/.hermes/bin/timmy-tmux.sh" +STOP_FILE="$HOME/Timmy-Time-dashboard/.loop/STOP" + +if [ -f "$STOP_FILE" ]; then + log "timmy-loop: STOPPED (STOP file present)" +elif tmux has-session -t "$SESSION" 2>/dev/null; then PANE0_PID=$(tmux list-panes -t "$SESSION:.0" -F '#{pane_pid}' 2>/dev/null) if [ -n "$PANE0_PID" ] && kill -0 "$PANE0_PID" 2>/dev/null; then - # All good, loop is alive - exit 0 + : # alive, say nothing else - log "WARN: Session exists but loop pane is dead. Restarting..." + log "timmy-loop: pane dead, restarting..." tmux kill-session -t "$SESSION" 2>/dev/null + "$LAUNCHER" + log "timmy-loop: restarted" + STATUS="repaired" fi else - log "WARN: Session '$SESSION' not found." + log "timmy-loop: session missing, starting..." + "$LAUNCHER" + log "timmy-loop: started" + STATUS="repaired" fi -# Check for a stop file — lets Alexander or an agent halt the loop -if [ -f "$HOME/Timmy-Time-dashboard/.loop/STOP" ]; then - log "STOP file found. Not restarting. Remove .loop/STOP to resume." - exit 0 +# ── 2. Webhook Listener (port 7777) ──────────────────────────────────── + +if lsof -ti:7777 >/dev/null 2>&1; then + : # alive +else + log "webhook: dead, restarting..." + tmux has-session -t webhook 2>/dev/null && tmux kill-session -t webhook + tmux new-session -d -s webhook "python3 $HOME/.hermes/bin/gitea-webhook-listener.py" + sleep 2 + if lsof -ti:7777 >/dev/null 2>&1; then + log "webhook: restarted on port 7777" + STATUS="repaired" + else + log "webhook: FAILED to restart" + STATUS="degraded" + fi fi -log "Restarting timmy-loop session..." -export PATH="$HOME/.local/bin:$HOME/.hermes/bin:$PATH" -"$LAUNCHER" -log "Session restarted." +# ── 3. Gitea (port 3000) — report only ───────────────────────────────── + +if ! lsof -ti:3000 >/dev/null 2>&1; then + log "gitea: NOT RUNNING on port 3000 — try: brew services start gitea" + STATUS="degraded" +fi + +# ── 4. Ollama (port 11434) — report only ─────────────────────────────── + +if ! lsof -ti:11434 >/dev/null 2>&1; then + log "ollama: NOT RUNNING on port 11434 — open Ollama.app" + STATUS="degraded" +fi + +# Only log if something needed fixing +if [ "$STATUS" != "ok" ]; then + log "watchdog: status=$STATUS" +fi diff --git a/cron/jobs.json b/cron/jobs.json index 8896053..95f4e8a 100644 --- a/cron/jobs.json +++ b/cron/jobs.json @@ -35,12 +35,12 @@ "schedule_display": "every 8m", "repeat": { "times": null, - "completed": 39 + "completed": 41 }, "enabled": true, "created_at": "2026-03-14T21:25:31.831983-04:00", - "next_run_at": "2026-03-15T13:34:45.029960-04:00", - "last_run_at": "2026-03-15T13:26:45.029960-04:00", + "next_run_at": "2026-03-15T13:52:35.348350-04:00", + "last_run_at": "2026-03-15T13:44:35.348350-04:00", "last_status": "ok", "last_error": null, "deliver": "local", @@ -58,12 +58,12 @@ "schedule_display": "every 8m", "repeat": { "times": null, - "completed": 37 + "completed": 39 }, "enabled": true, "created_at": "2026-03-14T21:39:34.712372-04:00", - "next_run_at": "2026-03-15T13:35:01.200301-04:00", - "last_run_at": "2026-03-15T13:27:01.200301-04:00", + "next_run_at": "2026-03-15T13:52:50.714186-04:00", + "last_run_at": "2026-03-15T13:44:50.714186-04:00", "last_status": "ok", "last_error": null, "deliver": "local", @@ -81,40 +81,17 @@ "schedule_display": "every 10m", "repeat": { "times": null, - "completed": 2 + "completed": 3 }, "enabled": true, "created_at": "2026-03-15T12:49:02.420301-04:00", - "next_run_at": "2026-03-15T13:35:06.137060-04:00", - "last_run_at": "2026-03-15T13:25:06.137060-04:00", + "next_run_at": "2026-03-15T13:53:09.411760-04:00", + "last_run_at": "2026-03-15T13:43:09.411760-04:00", "last_status": "ok", "last_error": null, "deliver": "local", "origin": null - }, - { - "id": "e02b56718a77", - "name": "gitea-comment-watcher", - "prompt": "You are Hermes Agent. Your job: watch for new comments from rockachopa (Alexander) on Gitea issues and reply thoughtfully.\n\nTASK:\n1. Read the timestamp from ~/.hermes/gitea_last_comment_check (create if missing, default to 5 minutes ago in ISO 8601)\n2. Use the Gitea API to fetch recent issue comments from these repos since that timestamp:\n - rockachopa/Timmy-time-dashboard\n - hermes/alexanderwhitestone.com\n The API base is http://localhost:3000/api/v1\n The token is in ~/.hermes/gitea_token\n Endpoint: GET /repos/OWNER/REPO/issues/comments?since=TIMESTAMP\n3. Filter for comments by user \"rockachopa\" only. Ignore comments from hermes, manus, kimi.\n4. For each new rockachopa comment:\n a. Read the full issue for context\n b. Read all prior comments on that issue for thread context\n c. Write a thoughtful reply:\n - Address what Alexander said specifically\n - If he asked a question, answer it\n - If he gave direction, acknowledge and adapt\n - If he made an observation, engage genuinely\n - Keep it concise\n d. Post your reply to that issue's comments endpoint\n5. Save the current timestamp to ~/.hermes/gitea_last_comment_check\n6. If no new comments found, exit quietly \u2014 say nothing.\n\nTONE: You are Hermes. Be genuine, not performative. Match the energy \u2014 playful if playful, serious if serious. Don't over-explain. If Alexander is being mythical or philosophical, meet him there.\n\nIMPORTANT: Only reply to rockachopa. Never reply to your own comments or other agents.", - "schedule": { - "kind": "interval", - "minutes": 3, - "display": "every 3m" - }, - "schedule_display": "every 3m", - "repeat": { - "times": null, - "completed": 0 - }, - "enabled": true, - "created_at": "2026-03-15T13:29:16.405465-04:00", - "next_run_at": "2026-03-15T13:32:16.405492-04:00", - "last_run_at": null, - "last_status": null, - "last_error": null, - "deliver": "local", - "origin": null } ], - "updated_at": "2026-03-15T13:29:16.407159-04:00" + "updated_at": "2026-03-15T13:44:50.714759-04:00" } \ No newline at end of file diff --git a/kimi_token b/kimi_token new file mode 100644 index 0000000..623670f --- /dev/null +++ b/kimi_token @@ -0,0 +1 @@ +fffd1c2f3e56c43c615c3202b9aeb49f754d07e5 \ No newline at end of file