diff --git a/bin/timmy-orchestrator.sh b/bin/timmy-orchestrator.sh deleted file mode 100755 index 2f353aec..00000000 --- a/bin/timmy-orchestrator.sh +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env bash -# timmy-orchestrator.sh — Timmy's orchestration loop -# Uses hermes (local Ollama) to triage, assign, review, and merge. -# Timmy is the brain. Claude/Gemini/Kimi are the hands. - -set -uo pipefail - -LOG_DIR="$HOME/.hermes/logs" -LOG="$LOG_DIR/timmy-orchestrator.log" -PIDFILE="$LOG_DIR/timmy-orchestrator.pid" -GITEA_URL="http://143.198.27.163:3000" -GITEA_TOKEN=$(cat "$HOME/.hermes/gitea_token_vps" 2>/dev/null) # Timmy token, NOT rockachopa -CYCLE_INTERVAL=300 -HERMES_TIMEOUT=180 - -mkdir -p "$LOG_DIR" - -# Single instance guard -if [ -f "$PIDFILE" ]; then - old_pid=$(cat "$PIDFILE") - if kill -0 "$old_pid" 2>/dev/null; then - echo "Timmy already running (PID $old_pid)" >&2 - exit 0 - fi -fi -echo $$ > "$PIDFILE" -trap 'rm -f "$PIDFILE"' EXIT - -log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] TIMMY: $*" >> "$LOG" -} - -REPOS="Timmy_Foundation/the-nexus Timmy_Foundation/autolora" - -gather_state() { - local state_dir="/tmp/timmy-state-$$" - mkdir -p "$state_dir" - - > "$state_dir/unassigned.txt" - > "$state_dir/open_prs.txt" - > "$state_dir/agent_status.txt" - - for repo in $REPOS; do - local short=$(echo "$repo" | cut -d/ -f2) - - # Unassigned issues - curl -sf -H "Authorization: token $GITEA_TOKEN" \ - "$GITEA_URL/api/v1/repos/$repo/issues?state=open&type=issues&limit=50" 2>/dev/null | \ - python3 -c " -import sys,json -for i in json.load(sys.stdin): - if not i.get('assignees'): - print(f'REPO={\"$repo\"} NUM={i[\"number\"]} TITLE={i[\"title\"]}')" >> "$state_dir/unassigned.txt" 2>/dev/null - - # Open PRs - curl -sf -H "Authorization: token $GITEA_TOKEN" \ - "$GITEA_URL/api/v1/repos/$repo/pulls?state=open&limit=30" 2>/dev/null | \ - python3 -c " -import sys,json -for p in json.load(sys.stdin): - print(f'REPO={\"$repo\"} PR={p[\"number\"]} BY={p[\"user\"][\"login\"]} TITLE={p[\"title\"]}')" >> "$state_dir/open_prs.txt" 2>/dev/null - done - - echo "Claude workers: $(pgrep -f 'claude.*--print.*--dangerously' 2>/dev/null | wc -l | tr -d ' ')" >> "$state_dir/agent_status.txt" - echo "Claude loop: $(pgrep -f 'claude-loop.sh' 2>/dev/null | wc -l | tr -d ' ') procs" >> "$state_dir/agent_status.txt" - tail -50 "$LOG_DIR/claude-loop.log" 2>/dev/null | grep -c "SUCCESS" | xargs -I{} echo "Recent successes: {}" >> "$state_dir/agent_status.txt" - tail -50 "$LOG_DIR/claude-loop.log" 2>/dev/null | grep -c "FAILED" | xargs -I{} echo "Recent failures: {}" >> "$state_dir/agent_status.txt" - - echo "$state_dir" -} - -run_triage() { - local state_dir="$1" - local unassigned_count=$(wc -l < "$state_dir/unassigned.txt" | tr -d ' ') - local pr_count=$(wc -l < "$state_dir/open_prs.txt" | tr -d ' ') - - log "Cycle: $unassigned_count unassigned, $pr_count open PRs" - - # If nothing to do, skip the LLM call - if [ "$unassigned_count" -eq 0 ] && [ "$pr_count" -eq 0 ]; then - log "Nothing to triage" - return - fi - - # Phase 1: Bulk-assign unassigned issues to claude (no LLM needed) - if [ "$unassigned_count" -gt 0 ]; then - log "Assigning $unassigned_count issues to claude..." - while IFS= read -r line; do - local repo=$(echo "$line" | sed 's/.*REPO=\([^ ]*\).*/\1/') - local num=$(echo "$line" | sed 's/.*NUM=\([^ ]*\).*/\1/') - curl -sf -X PATCH "$GITEA_URL/api/v1/repos/$repo/issues/$num" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"assignees":["claude"]}' >/dev/null 2>&1 && \ - log " Assigned #$num ($repo) to claude" - done < "$state_dir/unassigned.txt" - fi - - # Phase 2: PR review via Timmy (LLM) - if [ "$pr_count" -gt 0 ]; then - run_pr_review "$state_dir" - fi -} - -run_pr_review() { - local state_dir="$1" - local prompt_file="/tmp/timmy-prompt-$$.txt" - - # Build a review prompt listing all open PRs - cat > "$prompt_file" <<'HEADER' -You are Timmy, the orchestrator. Review these open PRs from AI agents. - -For each PR, you will see the diff. Your job: -- MERGE if changes look reasonable (most agent PRs are good, merge aggressively) -- COMMENT if there is a clear problem -- CLOSE if it is a duplicate or garbage - -Use these exact curl patterns (replace REPO, NUM): - Merge: curl -sf -X POST "GITEA/api/v1/repos/REPO/pulls/NUM/merge" -H "Authorization: token TOKEN" -H "Content-Type: application/json" -d '{"Do":"squash"}' - Comment: curl -sf -X POST "GITEA/api/v1/repos/REPO/pulls/NUM/comments" -H "Authorization: token TOKEN" -H "Content-Type: application/json" -d '{"body":"feedback"}' - Close: curl -sf -X PATCH "GITEA/api/v1/repos/REPO/pulls/NUM" -H "Authorization: token TOKEN" -H "Content-Type: application/json" -d '{"state":"closed"}' - -HEADER - - # Replace placeholders - sed -i '' "s|GITEA|$GITEA_URL|g; s|TOKEN|$GITEA_TOKEN|g" "$prompt_file" - - # Add each PR with its diff (up to 10 PRs per cycle) - local count=0 - while IFS= read -r line && [ "$count" -lt 10 ]; do - local repo=$(echo "$line" | sed 's/.*REPO=\([^ ]*\).*/\1/') - local pr_num=$(echo "$line" | sed 's/.*PR=\([^ ]*\).*/\1/') - local by=$(echo "$line" | sed 's/.*BY=\([^ ]*\).*/\1/') - local title=$(echo "$line" | sed 's/.*TITLE=//') - - [ -z "$pr_num" ] && continue - - local diff - diff=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \ - -H "Accept: application/diff" \ - "$GITEA_URL/api/v1/repos/$repo/pulls/$pr_num" 2>/dev/null | head -150) - - [ -z "$diff" ] && continue - - echo "" >> "$prompt_file" - echo "=== PR #$pr_num in $repo by $by ===" >> "$prompt_file" - echo "Title: $title" >> "$prompt_file" - echo "Diff (first 150 lines):" >> "$prompt_file" - echo "$diff" >> "$prompt_file" - echo "=== END PR #$pr_num ===" >> "$prompt_file" - - count=$((count + 1)) - done < "$state_dir/open_prs.txt" - - if [ "$count" -eq 0 ]; then - rm -f "$prompt_file" - return - fi - - echo "" >> "$prompt_file" - echo "Review each PR above. Execute curl commands for your decisions. Be brief." >> "$prompt_file" - - local prompt_text - prompt_text=$(cat "$prompt_file") - rm -f "$prompt_file" - - log "Reviewing $count PRs..." - local result - result=$(timeout "$HERMES_TIMEOUT" hermes chat -q "$prompt_text" -Q --yolo 2>&1) - local exit_code=$? - - if [ "$exit_code" -eq 0 ]; then - log "PR review complete" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $result" >> "$LOG_DIR/timmy-reviews.log" - else - log "PR review failed (exit $exit_code)" - fi -} - -# === MAIN LOOP === -log "=== Timmy Orchestrator Started (PID $$) ===" -log "Model: qwen3:30b via Ollama | Cycle: ${CYCLE_INTERVAL}s" - -WORKFORCE_CYCLE=0 - -while true; do - state_dir=$(gather_state) - run_triage "$state_dir" - rm -rf "$state_dir" - - # Run workforce manager every 3rd cycle (~15 min) - WORKFORCE_CYCLE=$((WORKFORCE_CYCLE + 1)) - if [ $((WORKFORCE_CYCLE % 3)) -eq 0 ]; then - log "Running workforce manager..." - python3 "$HOME/.hermes/bin/workforce-manager.py" all >> "$LOG_DIR/workforce-manager.log" 2>&1 - log "Workforce manager complete" - fi - - log "Sleeping ${CYCLE_INTERVAL}s" - sleep "$CYCLE_INTERVAL" -done