#!/bin/bash
# kimi-heartbeat.sh — Polls Gitea for assigned-kimi issues, dispatches to KimiClaw via OpenClaw
# Zero LLM cost for polling — only calls kimi/kimi-code for actual work.
#
# Run manually:  bash ~/.timmy/uniwizard/kimi-heartbeat.sh
# Runs via launchd every 5 minutes: ai.timmy.kimi-heartbeat.plist
#
# Workflow for humans:
#   1. Create or open a Gitea issue in any tracked repo
#   2. Add the "assigned-kimi" label
#   3. This script picks it up, dispatches to KimiClaw, posts results back
#   4. Label transitions: assigned-kimi → kimi-in-progress → kimi-done
#
# PLANNING: If the issue body is >500 chars or contains "##" headers,
# KimiClaw first runs a 2-minute planning pass to decompose the task.
# If it needs subtasks, it creates child issues and labels them assigned-kimi
# for the next heartbeat cycle. This prevents 10-minute timeouts on complex work.

set -euo pipefail

# --- Config ---
TOKEN=$(cat "$HOME/.timmy/kimi_gitea_token" | tr -d '[:space:]')
TIMMY_TOKEN=$(cat "$HOME/.config/gitea/timmy-token" | tr -d '[:space:]')
# Prefer Tailscale (private network) over public IP
if curl -sf --connect-timeout 2 "http://100.126.61.75:3000/api/v1/version" > /dev/null 2>&1; then
  BASE="http://100.126.61.75:3000/api/v1"
else
  BASE="http://143.198.27.163:3000/api/v1"
fi
LOG="/tmp/kimi-heartbeat.log"
LOCKFILE="/tmp/kimi-heartbeat.lock"
MAX_DISPATCH=5  # Don't overwhelm Kimi with too many parallel tasks
PLAN_TIMEOUT=120   # 2 minutes for planning pass
EXEC_TIMEOUT=480   # 8 minutes for execution pass
BODY_COMPLEXITY_THRESHOLD=500  # chars — above this triggers planning

REPOS=(
  "Timmy_Foundation/timmy-home"
  "Timmy_Foundation/timmy-config"
  "Timmy_Foundation/the-nexus"
  "Timmy_Foundation/hermes-agent"
)

# --- Helpers ---
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

# Prevent overlapping runs
if [ -f "$LOCKFILE" ]; then
  lock_age=$(( $(date +%s) - $(stat -f %m "$LOCKFILE" 2>/dev/null || echo 0) ))
  if [ "$lock_age" -lt 600 ]; then
    log "SKIP: previous run still active (lock age: ${lock_age}s)"
    exit 0
  else
    log "WARN: stale lock (${lock_age}s), removing"
    rm -f "$LOCKFILE"
  fi
fi
trap 'rm -f "$LOCKFILE"' EXIT
touch "$LOCKFILE"

dispatched=0

for repo in "${REPOS[@]}"; do
  # Fetch open issues with assigned-kimi label
  response=$(curl -sf -H "Authorization: token $TIMMY_TOKEN" \
    "$BASE/repos/$repo/issues?state=open&labels=assigned-kimi&limit=20" 2>/dev/null || echo "[]")

  # Filter: skip issues that already have kimi-in-progress or kimi-done
  issues=$(echo "$response" | python3 -c "
import json, sys
try:
    data = json.loads(sys.stdin.buffer.read())
except:
    sys.exit(0)
for i in data:
    labels = [l['name'] for l in i.get('labels', [])]
    if 'kimi-in-progress' in labels or 'kimi-done' in labels:
        continue
    # Pipe-delimited: number|title|body_length|body (truncated, newlines removed)
    body = (i.get('body', '') or '')
    body_len = len(body)
    body_clean = body[:1500].replace('\n', ' ').replace('|', ' ')
    title = i['title'].replace('|', ' ')
    print(f\"{i['number']}|{title}|{body_len}|{body_clean}\")
" 2>/dev/null)

  [ -z "$issues" ] && continue

  while IFS='|' read -r issue_num title body_len body; do
    [ -z "$issue_num" ] && continue
    log "FOUND: $repo #$issue_num — $title (body: ${body_len} chars)"

    # --- Get label IDs for this repo ---
    label_json=$(curl -sf -H "Authorization: token $TIMMY_TOKEN" \
      "$BASE/repos/$repo/labels" 2>/dev/null || echo "[]")
    
    progress_id=$(echo "$label_json" | python3 -c "import json,sys; [print(l['id']) for l in json.load(sys.stdin) if l['name']=='kimi-in-progress']" 2>/dev/null)
    done_id=$(echo "$label_json" | python3 -c "import json,sys; [print(l['id']) for l in json.load(sys.stdin) if l['name']=='kimi-done']" 2>/dev/null)
    kimi_id=$(echo "$label_json" | python3 -c "import json,sys; [print(l['id']) for l in json.load(sys.stdin) if l['name']=='assigned-kimi']" 2>/dev/null)

    # --- Add kimi-in-progress label ---
    if [ -n "$progress_id" ]; then
      curl -sf -X POST -H "Authorization: token $TIMMY_TOKEN" -H "Content-Type: application/json" \
        -d "{\"labels\":[$progress_id]}" \
        "$BASE/repos/$repo/issues/$issue_num/labels" > /dev/null 2>&1 || true
    fi

    # --- Decide: plan first or execute directly ---
    needs_planning=false
    if [ "$body_len" -gt "$BODY_COMPLEXITY_THRESHOLD" ]; then
      needs_planning=true
    fi

    if [ "$needs_planning" = true ]; then
      # =============================================
      # PHASE 1: PLANNING PASS (2 min timeout)
      # =============================================
      log "PLAN: $repo #$issue_num — complex task, running planning pass"

      curl -sf -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
        -d "{\"body\":\"🟠 **KimiClaw picking up this task** via heartbeat.\\nBackend: kimi/kimi-code (Moonshot AI)\\nMode: **Planning first** (task is complex)\\nTimestamp: $(date -u '+%Y-%m-%dT%H:%M:%SZ')\"}" \
        "$BASE/repos/$repo/issues/$issue_num/comments" > /dev/null 2>&1 || true

      plan_prompt="You are KimiClaw, a planning agent. You have 2 MINUTES.

TASK: Analyze this Gitea issue and decide if you can complete it in under 8 minutes, or if it needs to be broken into subtasks.

ISSUE #$issue_num in $repo: $title

BODY:
$body

RULES:
- If you CAN complete this in one pass (research, write analysis, answer a question): respond with EXECUTE followed by a one-line plan.
- If the task is TOO BIG (needs git operations, multiple repos, >2000 words of output, or multi-step implementation): respond with DECOMPOSE followed by a numbered list of 2-5 smaller subtasks. Each subtask must be completable in under 8 minutes by itself.
- Each subtask line format: SUBTASK: <title> | <one-line description>
- Be realistic about what fits in 8 minutes with no terminal access.
- You CANNOT clone repos, run git, or execute code. You CAN research, analyze, write specs, review code via API, and produce documents.

Respond with ONLY your decision. No preamble."

      plan_result=$(openclaw agent --agent main --message "$plan_prompt" --timeout $PLAN_TIMEOUT --json 2>/dev/null || echo '{"status":"error"}')
      plan_status=$(echo "$plan_result" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','error'))" 2>/dev/null || echo "error")
      plan_text=$(echo "$plan_result" | python3 -c "
import json,sys
d = json.load(sys.stdin)
payloads = d.get('result',{}).get('payloads',[])
print(payloads[0]['text'] if payloads else '')
" 2>/dev/null || echo "")

      if echo "$plan_text" | grep -qi "^DECOMPOSE"; then
        # --- Create subtask issues ---
        log "DECOMPOSE: $repo #$issue_num — creating subtasks"

        # Post the plan as a comment
        escaped_plan=$(echo "$plan_text" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null)
        curl -sf -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
          -d "{\"body\":\"📋 **Planning complete — decomposing into subtasks:**\\n\\n$plan_text\"}" \
          "$BASE/repos/$repo/issues/$issue_num/comments" > /dev/null 2>&1 || true

        # Extract SUBTASK lines and create child issues
        echo "$plan_text" | grep -i "^SUBTASK:" | head -5 | while IFS='|' read -r sub_title sub_desc; do
          sub_title=$(echo "$sub_title" | sed 's/^SUBTASK: *//')
          sub_desc=$(echo "${sub_desc:-$sub_title}" | sed 's/^ *//')
          
          if [ -n "$sub_title" ]; then
            sub_body="## Parent Issue\\nChild of #$issue_num: $title\\n\\n## Task\\n$sub_desc\\n\\n## Constraints\\n- Must complete in under 8 minutes\\n- No git/terminal operations\\n- Post results as analysis/documentation\\n\\n## Assignee\\n@KimiClaw"
            
            curl -sf -X POST -H "Authorization: token $TIMMY_TOKEN" -H "Content-Type: application/json" \
              -d "{\"title\":\"[SUB] $sub_title\",\"body\":\"$sub_body\"}" \
              "$BASE/repos/$repo/issues" > /dev/null 2>&1

            # Get the issue number of what we just created and label it
            new_num=$(curl -sf -H "Authorization: token $TIMMY_TOKEN" \
              "$BASE/repos/$repo/issues?state=open&limit=1&type=issues" | \
              python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['number'] if d else '')" 2>/dev/null)
            
            if [ -n "$new_num" ] && [ -n "$kimi_id" ]; then
              curl -sf -X POST -H "Authorization: token $TIMMY_TOKEN" -H "Content-Type: application/json" \
                -d "{\"labels\":[$kimi_id]}" \
                "$BASE/repos/$repo/issues/$new_num/labels" > /dev/null 2>&1 || true
              log "SUBTASK: $repo #$new_num — $sub_title"
            fi
          fi
        done

        # Mark parent as kimi-done (subtasks will be picked up next cycle)
        [ -n "$progress_id" ] && curl -sf -X DELETE -H "Authorization: token $TIMMY_TOKEN" \
          "$BASE/repos/$repo/issues/$issue_num/labels/$progress_id" > /dev/null 2>&1 || true
        [ -n "$done_id" ] && curl -sf -X POST -H "Authorization: token $TIMMY_TOKEN" -H "Content-Type: application/json" \
          -d "{\"labels\":[$done_id]}" \
          "$BASE/repos/$repo/issues/$issue_num/labels" > /dev/null 2>&1 || true

        dispatched=$((dispatched + 1))
        log "PLANNED: $repo #$issue_num — subtasks created, parent marked done"

      else
        # --- Plan says EXECUTE — proceed to execution ---
        log "EXECUTE: $repo #$issue_num — planning pass says single-pass OK"
        # Fall through to execution below
        needs_planning=false
      fi
    fi

    if [ "$needs_planning" = false ]; then
      # =============================================
      # PHASE 2: EXECUTION PASS (8 min timeout)
      # =============================================
      
      # Post pickup comment if we didn't already (simple tasks skip planning)
      if [ "$body_len" -le "$BODY_COMPLEXITY_THRESHOLD" ]; then
        curl -sf -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
          -d "{\"body\":\"🟠 **KimiClaw picking up this task** via heartbeat.\\nBackend: kimi/kimi-code (Moonshot AI)\\nMode: **Direct execution** (task fits in one pass)\\nTimestamp: $(date -u '+%Y-%m-%dT%H:%M:%SZ')\"}" \
          "$BASE/repos/$repo/issues/$issue_num/comments" > /dev/null 2>&1 || true
      fi

      log "DISPATCH: $repo #$issue_num to openclaw (timeout: ${EXEC_TIMEOUT}s)"

      exec_prompt="You are KimiClaw, an AI agent powered by Kimi K2.5 (Moonshot AI).
You are working on Gitea issue #$issue_num in repo $repo.
You have 8 MINUTES maximum. Be concise and focused.

ISSUE TITLE: $title

ISSUE BODY:
$body

YOUR TASK:
1. Read the issue carefully and do the work described
2. Stay focused — deliver the core ask, skip nice-to-haves
3. Provide your COMPLETE results as your response (use markdown)
4. If you realize mid-task this will take longer than 8 minutes, STOP and summarize what you've done so far plus what remains"

      # --- Dispatch to OpenClaw (background) ---
      (
        result=$(openclaw agent --agent main --message "$exec_prompt" --timeout $EXEC_TIMEOUT --json 2>/dev/null || echo '{"status":"error"}')
        status=$(echo "$result" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','error'))" 2>/dev/null || echo "error")
        
        # Extract response text
        response_text=$(echo "$result" | python3 -c "
import json,sys
d = json.load(sys.stdin)
payloads = d.get('result',{}).get('payloads',[])
print(payloads[0]['text'][:3000] if payloads else 'No response')
" 2>/dev/null || echo "No response")

        if [ "$status" = "ok" ] && [ "$response_text" != "No response" ]; then
          log "COMPLETED: $repo #$issue_num"

          # Post result as comment (escape for JSON)
          escaped=$(echo "$response_text" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read())[1:-1])" 2>/dev/null)
          curl -sf -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
            -d "{\"body\":\"✅ **KimiClaw result:**\\n\\n$escaped\"}" \
            "$BASE/repos/$repo/issues/$issue_num/comments" > /dev/null 2>&1 || true

          # Remove kimi-in-progress, add kimi-done
          [ -n "$progress_id" ] && curl -sf -X DELETE -H "Authorization: token $TIMMY_TOKEN" \
            "$BASE/repos/$repo/issues/$issue_num/labels/$progress_id" > /dev/null 2>&1 || true
          [ -n "$done_id" ] && curl -sf -X POST -H "Authorization: token $TIMMY_TOKEN" -H "Content-Type: application/json" \
            -d "{\"labels\":[$done_id]}" \
            "$BASE/repos/$repo/issues/$issue_num/labels" > /dev/null 2>&1 || true
        else
          log "FAILED: $repo #$issue_num — status=$status"
          
          curl -sf -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
            -d "{\"body\":\"🔴 **KimiClaw failed/timed out.**\\nStatus: $status\\nTimestamp: $(date -u '+%Y-%m-%dT%H:%M:%SZ')\\n\\nTask may be too complex for single-pass execution. Consider breaking into smaller subtasks.\"}" \
            "$BASE/repos/$repo/issues/$issue_num/comments" > /dev/null 2>&1 || true

          # Remove kimi-in-progress on failure
          [ -n "$progress_id" ] && curl -sf -X DELETE -H "Authorization: token $TIMMY_TOKEN" \
            "$BASE/repos/$repo/issues/$issue_num/labels/$progress_id" > /dev/null 2>&1 || true
        fi
      ) &

      dispatched=$((dispatched + 1))
      log "DISPATCHED: $repo #$issue_num (background PID $!)"
    fi

    # Enforce dispatch cap
    if [ "$dispatched" -ge "$MAX_DISPATCH" ]; then
      log "CAPPED: reached $MAX_DISPATCH dispatches, remaining issues deferred to next heartbeat"
      break 2  # Break out of both loops
    fi

    # Stagger dispatches to avoid overwhelming kimi
    sleep 3

  done <<< "$issues"
done

if [ "$dispatched" -eq 0 ]; then
  log "Heartbeat: no pending tasks"
else
  log "Heartbeat: dispatched $dispatched task(s)"
fi
