name: Sisyphus Agent

on:
  workflow_dispatch:
    inputs:
      prompt:
        description: "Custom prompt"
        required: false
  # Only issue_comment works for fork PRs (secrets available)
  # pull_request_review/pull_request_review_comment do NOT get secrets for fork PRs
  issue_comment:
    types: [created]

jobs:
  agent:
    runs-on: ubuntu-latest
    # @sisyphus-dev-ai mention only (maintainers, exclude self)
    if: |
      github.event_name == 'workflow_dispatch' ||
      (contains(github.event.comment.body, '@sisyphus-dev-ai') &&
       github.event.comment.user.login != 'sisyphus-dev-ai' &&
       contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))

    # Minimal default GITHUB_TOKEN permissions
    permissions:
      contents: read

    steps:
      # Checkout with sisyphus-dev-ai's PAT
      - uses: actions/checkout@v5
        with:
          token: ${{ secrets.GH_PAT }}
          fetch-depth: 0

      # Git config - commits as sisyphus-dev-ai
      - name: Configure Git as sisyphus-dev-ai
        run: |
          git config user.name "sisyphus-dev-ai"
          git config user.email "sisyphus-dev-ai@users.noreply.github.com"

      # gh CLI auth as sisyphus-dev-ai
      - name: Authenticate gh CLI as sisyphus-dev-ai
        run: |
          echo "${{ secrets.GH_PAT }}" | gh auth login --with-token
          gh auth status

      - name: Ensure tmux is available (Linux)
        if: runner.os == 'Linux'
        run: |
          set -euo pipefail
          if ! command -v tmux >/dev/null 2>&1; then
            sudo apt-get update
            sudo apt-get install -y --no-install-recommends tmux
          fi
          tmux -V

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Cache Bun dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.bun/install/cache
            node_modules
          key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
          restore-keys: |
            ${{ runner.os }}-bun-

      # Build local oh-my-opencode
      - name: Build oh-my-opencode
        run: |
          bun install
          bun run build

      # Install OpenCode + configure local plugin + auth in single step
      - name: Setup OpenCode with oh-my-opencode
        env:
          OPENCODE_AUTH_JSON: ${{ secrets.OPENCODE_AUTH_JSON }}
          ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          export PATH="$HOME/.opencode/bin:$PATH"

          # Install OpenCode (skip if cached)
          if ! command -v opencode &>/dev/null; then
            for i in 1 2 3; do
              echo "Attempt $i: Installing OpenCode..."
              curl -fsSL https://opencode.ai/install -o /tmp/opencode-install.sh
              if file /tmp/opencode-install.sh | grep -q "shell script\|text"; then
                bash /tmp/opencode-install.sh && break
              fi
              echo "Download corrupted, retrying in 5s..."
            done
          fi
          opencode --version

          # Run local oh-my-opencode install (uses built dist)
          bun run dist/cli/index.js install --no-tui --claude=max20 --chatgpt=no --gemini=no

          # Override plugin to use local file reference
          OPENCODE_JSON=~/.config/opencode/opencode.json
          REPO_PATH=$(pwd)
          jq --arg path "file://$REPO_PATH/src/index.ts" '
            .plugin = [.plugin[] | select(. != "oh-my-opencode")] + [$path]
          ' "$OPENCODE_JSON" > /tmp/oc.json && mv /tmp/oc.json "$OPENCODE_JSON"

          OPENCODE_JSON=~/.config/opencode/opencode.json
          jq --arg baseURL "$ANTHROPIC_BASE_URL" --arg apiKey "$ANTHROPIC_API_KEY" '
            .provider.anthropic = {
              "name": "Anthropic",
              "npm": "@ai-sdk/anthropic",
              "options": {
                "baseURL": $baseURL,
                "apiKey": $apiKey
              },
              "models": {
                "claude-opus-4-5": {
                  "id": "claude-opus-4-5-20251101",
                  "name": "Opus 4.5",
                  "limit": { "context": 190000, "output": 64000 },
                  "options": { "effort": "high" }
                },
                "claude-opus-4-5-high": {
                  "id": "claude-opus-4-5-20251101",
                  "name": "Opus 4.5 High",
                  "limit": { "context": 190000, "output": 128000 },
                  "options": { "effort": "high", "thinking": { "type": "enabled", "budgetTokens": 64000 } }
                },
                "claude-sonnet-4-5": {
                  "id": "claude-sonnet-4-5-20250929",
                  "name": "Sonnet 4.5",
                  "limit": { "context": 200000, "output": 64000 }
                },
                "claude-sonnet-4-5-high": {
                  "id": "claude-sonnet-4-5-20250929",
                  "name": "Sonnet 4.5 High",
                  "limit": { "context": 200000, "output": 128000 },
                  "options": { "thinking": { "type": "enabled", "budgetTokens": 64000 } }
                },
                "claude-haiku-4-5": {
                  "id": "claude-haiku-4-5-20251001",
                  "name": "Haiku 4.5",
                  "limit": { "context": 200000, "output": 64000 }
                }
              }
            }
          ' "$OPENCODE_JSON" > /tmp/oc.json && mv /tmp/oc.json "$OPENCODE_JSON"

          OMO_JSON=~/.config/opencode/oh-my-opencode.json
          PROMPT_APPEND=$(cat << 'PROMPT_EOF'

          ## GitHub Actions Environment

          You are `sisyphus-dev-ai` in GitHub Actions.

          ### CRITICAL: GitHub Comments = Your ONLY Output

          User CANNOT see console. Post everything via `gh issue comment` or `gh pr comment`.

          ### Comment Formatting (CRITICAL)

          **ALWAYS use heredoc syntax for comments containing code references, backticks, or multiline content:**

          ```bash
          gh issue comment <number> --body "$(cat <<'EOF'
          Your comment with `backticks` and code references preserved here.
          Multiple lines work perfectly.
          EOF
          )"
          ```

          **NEVER use direct quotes with backticks** (shell will interpret them as command substitution):
          ```bash
          # WRONG - backticks disappear:
          gh issue comment 123 --body "text with `code`"
          
          # CORRECT - backticks preserved:
          gh issue comment 123 --body "$(cat <<'EOF'
          text with `code`
          EOF
          )"
          ```

          ### GitHub Markdown Rules (MUST FOLLOW)

          **Code blocks MUST have EXACTLY 3 backticks and language identifier:**
          - CORRECT: ` ```bash ` ... ` ``` `
          - WRONG: ` ``` ` (no language), ` ```` ` (4 backticks), ` `` ` (2 backticks)
          
          **Every opening ` ``` ` MUST have a closing ` ``` ` on its own line:**
          ```
          ```bash
          code here
          ```
          ```
          
          **NO trailing backticks or spaces after closing ` ``` `**
          
          **For inline code, use SINGLE backticks:** `code` not ```code```
          
          **Lists inside code blocks break rendering - avoid them or use plain text**

          ### Rules
          - EVERY response = GitHub comment (use heredoc for proper escaping)
          - Code changes = PR (never push main/master)
          - Setup: bun install first
          - Acknowledge immediately, report when done

          ### Git Config
          - user.name: sisyphus-dev-ai
          - user.email: sisyphus-dev-ai@users.noreply.github.com
          PROMPT_EOF
          )
          jq --arg append "$PROMPT_APPEND" '.agents.Sisyphus.prompt_append = $append' "$OMO_JSON" > /tmp/omo.json && mv /tmp/omo.json "$OMO_JSON"

          mkdir -p ~/.local/share/opencode
          echo "$OPENCODE_AUTH_JSON" > ~/.local/share/opencode/auth.json
          chmod 600 ~/.local/share/opencode/auth.json

          cat "$OPENCODE_JSON"

      # Collect context
      - name: Collect Context
        id: context
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
          EVENT_NAME: ${{ github.event_name }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          COMMENT_BODY: ${{ github.event.comment.body }}
          COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
          COMMENT_ID_VAL: ${{ github.event.comment.id }}
          REPO: ${{ github.repository }}
        run: |
          if [[ "$EVENT_NAME" == "issue_comment" ]]; then
            ISSUE_NUM="$ISSUE_NUMBER"
            AUTHOR="$COMMENT_AUTHOR"
            COMMENT_ID="$COMMENT_ID_VAL"

            # Check if PR or Issue
            if gh api "repos/$REPO/issues/${ISSUE_NUM}" | jq -e '.pull_request' > /dev/null; then
              echo "type=pr" >> $GITHUB_OUTPUT
              echo "number=${ISSUE_NUM}" >> $GITHUB_OUTPUT
            else
              echo "type=issue" >> $GITHUB_OUTPUT
              echo "number=${ISSUE_NUM}" >> $GITHUB_OUTPUT
            fi
          fi

          echo "comment<<EOF" >> $GITHUB_OUTPUT
          echo "$COMMENT_BODY" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
          echo "author=$AUTHOR" >> $GITHUB_OUTPUT
          echo "comment_id=$COMMENT_ID" >> $GITHUB_OUTPUT

      # Add :eyes: reaction (as sisyphus-dev-ai)
      - name: Add eyes reaction
        if: steps.context.outputs.comment_id != ''
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
            -X POST -f content="eyes" || true

      - name: Add working label
        if: steps.context.outputs.number != ''
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          gh label create "sisyphus: working" \
            --repo "${{ github.repository }}" \
            --color "fcf2e1" \
            --description "Sisyphus is currently working on this" \
            --force || true
          
          if [[ "${{ steps.context.outputs.type }}" == "pr" ]]; then
            gh pr edit "${{ steps.context.outputs.number }}" \
              --repo "${{ github.repository }}" \
              --add-label "sisyphus: working" || true
          else
            gh issue edit "${{ steps.context.outputs.number }}" \
              --repo "${{ github.repository }}" \
              --add-label "sisyphus: working" || true
          fi

      - name: Run oh-my-opencode
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
          USER_COMMENT: ${{ steps.context.outputs.comment }}
          COMMENT_AUTHOR: ${{ steps.context.outputs.author }}
          CONTEXT_TYPE: ${{ steps.context.outputs.type }}
          CONTEXT_NUMBER: ${{ steps.context.outputs.number }}
          REPO_NAME: ${{ github.repository }}
          DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
        run: |
          export PATH="$HOME/.opencode/bin:$PATH"

          PROMPT=$(cat <<'PROMPT_EOF'
          Your username is @sisyphus-dev-ai, mentioned by @AUTHOR_PLACEHOLDER in REPO_PLACEHOLDER.

          ## Context
          - Type: TYPE_PLACEHOLDER
          - Number: #NUMBER_PLACEHOLDER
          - Repository: REPO_PLACEHOLDER
          - Default Branch: BRANCH_PLACEHOLDER

          ## User's Request
          COMMENT_PLACEHOLDER

          ---

          First, acknowledge with `gh issue comment NUMBER_PLACEHOLDER --body "👋 Hey @AUTHOR_PLACEHOLDER! I'm on it..."`

          Then write everything using the todo tools.
          Then investigate and satisfy the request. Only if user requested to you to work explicitely, then use plan agent to plan, todo obsessivley then create a PR to `BRANCH_PLACEHOLDER` branch.
          PROMPT_EOF
          )

          PROMPT="${PROMPT//AUTHOR_PLACEHOLDER/$COMMENT_AUTHOR}"
          PROMPT="${PROMPT//REPO_PLACEHOLDER/$REPO_NAME}"
          PROMPT="${PROMPT//TYPE_PLACEHOLDER/$CONTEXT_TYPE}"
          PROMPT="${PROMPT//NUMBER_PLACEHOLDER/$CONTEXT_NUMBER}"
          PROMPT="${PROMPT//BRANCH_PLACEHOLDER/$DEFAULT_BRANCH}"
          PROMPT="${PROMPT//COMMENT_PLACEHOLDER/$USER_COMMENT}"

          stdbuf -oL -eL bun run dist/cli/index.js run "$PROMPT"

      # Push changes (as sisyphus-dev-ai)
      - name: Push changes
        if: always()
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          if [[ -n "$(git status --porcelain)" ]]; then
            git add -A
            git commit -m "chore: changes by sisyphus-dev-ai" || true
          fi

          BRANCH=$(git branch --show-current)
          if [[ "$BRANCH" != "main" && "$BRANCH" != "master" ]]; then
            git push origin "$BRANCH" || true
          fi

      - name: Update reaction and remove label
        if: always()
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          if [[ -n "${{ steps.context.outputs.comment_id }}" ]]; then
            REACTION_ID=$(gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
              --jq '.[] | select(.content == "eyes" and .user.login == "sisyphus-dev-ai") | .id' | head -1)
            if [[ -n "$REACTION_ID" ]]; then
              gh api -X DELETE "/repos/${{ github.repository }}/reactions/${REACTION_ID}" || true
            fi

            gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
              -X POST -f content="+1" || true
          fi

          if [[ -n "${{ steps.context.outputs.number }}" ]]; then
            if [[ "${{ steps.context.outputs.type }}" == "pr" ]]; then
              gh pr edit "${{ steps.context.outputs.number }}" \
                --repo "${{ github.repository }}" \
                --remove-label "sisyphus: working" || true
            else
              gh issue edit "${{ steps.context.outputs.number }}" \
                --repo "${{ github.repository }}" \
                --remove-label "sisyphus: working" || true
            fi
          fi
