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
This commit is contained in:
94
bin/hermes-startup.sh
Executable file
94
bin/hermes-startup.sh
Executable file
@@ -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 ==="
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
1
kimi_token
Normal file
1
kimi_token
Normal file
@@ -0,0 +1 @@
|
||||
fffd1c2f3e56c43c615c3202b9aeb49f754d07e5
|
||||
Reference in New Issue
Block a user