name: Mantis Telegram Live

on:
  issue_comment:
    types: [created]
  workflow_dispatch:
    inputs:
      candidate_ref:
        description: Ref, tag, or SHA to verify with Telegram live QA
        required: true
        default: main
        type: string
      pr_number:
        description: Optional PR number to receive the QA evidence comment
        required: false
        type: string
      scenario:
        description: Optional comma-separated Telegram scenario ids
        required: false
        default: telegram-status-command
        type: string
      crabbox_provider:
        description: Crabbox provider for the desktop transcript capture
        required: false
        default: aws
        type: choice
        options:
          - aws
          - hetzner
      crabbox_lease_id:
        description: Optional existing Crabbox desktop/browser lease id or slug to reuse
        required: false
        type: string

permissions:
  actions: read
  contents: read
  issues: write
  pull-requests: write

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
  NODE_VERSION: "24.x"
  OPENCLAW_BUILD_PRIVATE_QA: "1"
  OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
  CRABBOX_REF: main
  CRABBOX_AWS_REGION: us-east-1
  CRABBOX_CAPACITY_REGIONS: us-east-1

jobs:
  authorize_actor:
    name: Authorize workflow actor
    if: >-
      ${{
        github.event_name == 'workflow_dispatch' ||
        (
          github.event_name == 'issue_comment' &&
          github.event.issue.pull_request &&
          (
            contains(github.event.comment.body, '@openclaw-mantis') ||
            contains(github.event.comment.body, '/openclaw-mantis')
          )
        )
      }}
    runs-on: ubuntu-24.04
    outputs:
      authorized: ${{ steps.permission.outputs.authorized }}
    steps:
      - name: Require maintainer-level repository access
        id: permission
        uses: actions/github-script@v8
        with:
          script: |
            const allowed = new Set(["admin", "maintain", "write"]);
            const { owner, repo } = context.repo;
            const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
              owner,
              repo,
              username: context.actor,
            });
            const permission = data.permission;
            core.info(`Actor ${context.actor} permission: ${permission}`);
            if (!allowed.has(permission)) {
              core.notice(
                `Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
              );
              core.setOutput("authorized", "false");
              return;
            }
            core.setOutput("authorized", "true");

  resolve_request:
    name: Resolve Mantis request
    needs: authorize_actor
    if: needs.authorize_actor.outputs.authorized == 'true'
    runs-on: ubuntu-24.04
    outputs:
      candidate_ref: ${{ steps.resolve.outputs.candidate_ref }}
      crabbox_provider: ${{ steps.resolve.outputs.crabbox_provider }}
      lease_id: ${{ steps.resolve.outputs.lease_id }}
      pr_number: ${{ steps.resolve.outputs.pr_number }}
      request_source: ${{ steps.resolve.outputs.request_source }}
      scenario: ${{ steps.resolve.outputs.scenario }}
      should_run: ${{ steps.resolve.outputs.should_run }}
    steps:
      - name: Resolve refs and target PR
        id: resolve
        uses: actions/github-script@v8
        with:
          script: |
            const eventName = context.eventName;

            function setOutput(name, value) {
              core.setOutput(name, value ?? "");
              core.info(`${name}=${value ?? ""}`);
            }

            if (eventName === "workflow_dispatch") {
              const inputs = context.payload.inputs ?? {};
              setOutput("should_run", "true");
              setOutput("candidate_ref", inputs.candidate_ref || "main");
              setOutput("pr_number", inputs.pr_number || "");
              setOutput("scenario", inputs.scenario || "telegram-status-command");
              setOutput("crabbox_provider", inputs.crabbox_provider || "aws");
              setOutput("lease_id", inputs.crabbox_lease_id || "");
              setOutput("request_source", "workflow_dispatch");
              return;
            }

            if (eventName !== "issue_comment") {
              core.setFailed(`Unsupported event: ${eventName}`);
              return;
            }

            const issue = context.payload.issue;
            const body = context.payload.comment?.body ?? "";
            if (!issue?.pull_request) {
              core.setFailed("Mantis issue_comment trigger requires a pull request comment.");
              return;
            }

            const normalized = body.toLowerCase();
            const requestedDesktopProof =
              normalized.includes("desktop proof") ||
              normalized.includes("desktop-proof") ||
              normalized.includes("telegram desktop") ||
              normalized.includes("native telegram") ||
              normalized.includes("visible proof") ||
              normalized.includes("visible-proof") ||
              normalized.includes("telegram-visible-proof");
            const requested =
              (normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
              normalized.includes("telegram") &&
              !requestedDesktopProof;
            if (!requested) {
              core.notice("Comment mentioned Mantis but did not request Telegram live QA.");
              setOutput("should_run", "false");
              setOutput("candidate_ref", "");
              setOutput("pr_number", "");
              setOutput("scenario", "");
              setOutput("crabbox_provider", "");
              setOutput("lease_id", "");
              setOutput("request_source", "unsupported_issue_comment");
              return;
            }

            const { owner, repo } = context.repo;
            const { data: pr } = await github.rest.pulls.get({
              owner,
              repo,
              pull_number: issue.number,
            });
            const candidateMatch = body.match(/(?:candidate|head)[\s:=]+([^\s`]+)/i);
            const scenarioMatch = body.match(/(?:scenario|scenarios)[\s:=]+([^\s`]+)/i);
            const providerMatch = body.match(/(?:provider|crabbox_provider)[\s:=]+([^\s`]+)/i);
            const leaseMatch = body.match(/(?:lease|lease_id|crabbox_lease_id)[\s:=]+([^\s`]+)/i);
            const rawCandidate = candidateMatch?.[1];
            const candidate =
              rawCandidate && !["head", "pr", "pr-head"].includes(rawCandidate.toLowerCase())
                ? rawCandidate
                : pr.head.sha;
            const provider = providerMatch?.[1] || "aws";
            if (!["aws", "hetzner"].includes(provider)) {
              core.setFailed(`Unsupported Crabbox provider for Mantis Telegram: ${provider}`);
              return;
            }

            setOutput("should_run", "true");
            setOutput("candidate_ref", candidate);
            setOutput("pr_number", String(issue.number));
            setOutput("scenario", scenarioMatch?.[1] || "telegram-status-command");
            setOutput("crabbox_provider", provider);
            setOutput("lease_id", leaseMatch?.[1] || "");
            setOutput("request_source", "issue_comment");

            await github.rest.reactions.createForIssueComment({
              owner,
              repo,
              comment_id: context.payload.comment.id,
              content: "eyes",
            }).catch((error) => core.warning(`Could not add eyes reaction: ${error.message}`));

  validate_ref:
    name: Validate candidate ref
    needs: resolve_request
    if: ${{ needs.resolve_request.outputs.should_run == 'true' }}
    runs-on: ubuntu-24.04
    outputs:
      candidate_revision: ${{ steps.validate.outputs.candidate_revision }}
    steps:
      - name: Checkout harness ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          fetch-depth: 0

      - name: Validate ref is trusted
        id: validate
        env:
          GH_TOKEN: ${{ github.token }}
          CANDIDATE_REF: ${{ needs.resolve_request.outputs.candidate_ref }}
        shell: bash
        run: |
          set -euo pipefail

          git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main

          revision="$(git rev-parse "${CANDIDATE_REF}^{commit}")"
          reason=""
          if git merge-base --is-ancestor "$revision" refs/remotes/origin/main; then
            reason="main-ancestor"
          elif git tag --points-at "$revision" | grep -Eq '^v'; then
            reason="release-tag"
          else
            pr_head_count="$(
              gh api \
                -H "Accept: application/vnd.github+json" \
                "repos/${GITHUB_REPOSITORY}/commits/${revision}/pulls" \
                --jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${revision}"'")] | length'
            )"
            if [[ "$pr_head_count" != "0" ]]; then
              reason="open-pr-head"
            fi
          fi

          if [[ -z "$reason" ]]; then
            echo "Candidate ref '${CANDIDATE_REF}' resolved to ${revision}, which is not trusted for this secret-bearing Mantis run." >&2
            exit 1
          fi

          echo "candidate_revision=${revision}" >> "$GITHUB_OUTPUT"
          {
            echo "candidate: \`${CANDIDATE_REF}\`"
            echo "candidate SHA: \`${revision}\`"
            echo "candidate trust reason: \`${reason}\`"
          } >> "$GITHUB_STEP_SUMMARY"

  run_telegram_live:
    name: Run Telegram live QA with Crabbox evidence
    needs: [resolve_request, validate_ref]
    if: ${{ needs.resolve_request.outputs.should_run == 'true' }}
    runs-on: ubuntu-24.04
    timeout-minutes: 180
    environment: qa-live-shared
    outputs:
      comparison_status: ${{ steps.run_mantis.outputs.comparison_status }}
      output_dir: ${{ steps.run_mantis.outputs.output_dir }}
    steps:
      - name: Wait for older Mantis Telegram account run
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          set -euo pipefail
          current_created="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" --jq .created_at)"
          stale_before="$(date -u -d '8 hours ago' +%Y-%m-%dT%H:%M:%SZ)"
          run_has_active_jobs() {
            local run_id="$1"
            local run_state="$2"
            if [[ "$run_state" != "in_progress" ]]; then
              return 0
            fi
            local active_jobs
            active_jobs="$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json jobs --jq '[.jobs[] | select(.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "pending" or .status == "requested")] | length')"
            [[ "$active_jobs" != "0" ]]
          }
          while true; do
            candidates="$(
              for workflow in mantis-telegram-desktop-proof.yml mantis-telegram-live.yml; do
                for status in queued in_progress waiting pending requested; do
                  gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --status "$status" --limit 100 --json databaseId,status,createdAt,url \
                    | jq -r \
                      --argjson current_id "$GITHUB_RUN_ID" \
                      --arg current_created "$current_created" \
                      --arg stale_before "$stale_before" \
                      '.[] | select(.databaseId != $current_id) | select(.createdAt >= $stale_before) | select(.createdAt < $current_created or (.createdAt == $current_created and .databaseId < $current_id)) | "\(.createdAt)\t#\(.databaseId)\t\(.status)\t\(.url)"'
                done
              done | sort -u
            )"
            blockers=""
            while IFS=$'\t' read -r created run_id run_state url; do
              if [[ -n "$run_id" ]] && run_has_active_jobs "${run_id#\#}" "$run_state"; then
                blockers+="${created}"$'\t'"${run_id}"$'\t'"${run_state}"$'\t'"${url}"$'\n'
              fi
            done <<<"$candidates"
            if [[ -z "$blockers" ]]; then
              break
            fi
            echo "Waiting for older Mantis Telegram account run:"
            printf '%s\n' "$blockers" | head -n 10
            sleep 60
          done

      - name: Checkout harness ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          fetch-depth: 0

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          node-version: ${{ env.NODE_VERSION }}
          install-bun: "true"

      - name: Build Mantis harness
        run: pnpm build

      - name: Cache Mantis candidate pnpm store
        uses: actions/cache@v4
        with:
          path: |
            ~/.local/share/pnpm/store
            ~/.cache/pnpm
          key: mantis-telegram-pnpm-${{ runner.os }}-${{ env.NODE_VERSION }}-${{ hashFiles('pnpm-lock.yaml') }}
          restore-keys: |
            mantis-telegram-pnpm-${{ runner.os }}-${{ env.NODE_VERSION }}-

      - name: Setup Go for Crabbox CLI
        uses: actions/setup-go@v6
        with:
          go-version: "1.26.x"
          cache: false

      - name: Install Crabbox CLI
        shell: bash
        run: |
          set -euo pipefail
          install_dir="${RUNNER_TEMP}/crabbox"
          mkdir -p "$install_dir/src" "$HOME/.local/bin"
          git init "$install_dir/src"
          git -C "$install_dir/src" remote add origin https://github.com/openclaw/crabbox.git
          git -C "$install_dir/src" fetch --depth 1 origin "$CRABBOX_REF"
          git -C "$install_dir/src" checkout --detach FETCH_HEAD
          go build -C "$install_dir/src" -o "$HOME/.local/bin/crabbox" ./cmd/crabbox
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
          "$HOME/.local/bin/crabbox" --version
          "$HOME/.local/bin/crabbox" warmup --help > "$install_dir/warmup-help.txt" 2>&1
          grep -q -- "-desktop" "$install_dir/warmup-help.txt"
          "$HOME/.local/bin/crabbox" media preview --help >/dev/null

      - name: Prepare candidate worktree
        env:
          CANDIDATE_SHA: ${{ needs.validate_ref.outputs.candidate_revision }}
        shell: bash
        run: |
          set -euo pipefail
          worktree_root=".artifacts/qa-e2e/mantis/telegram-live-worktrees"
          mkdir -p "$worktree_root"
          git worktree add --detach "$worktree_root/candidate" "$CANDIDATE_SHA"
          pnpm --dir "$worktree_root/candidate" install --frozen-lockfile --prefer-offline
          pnpm --dir "$worktree_root/candidate" build

      - name: Run Telegram live scenario and capture desktop evidence
        id: run_mantis
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
          OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
          OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
          CRABBOX_COORDINATOR: ${{ secrets.CRABBOX_COORDINATOR }}
          CRABBOX_COORDINATOR_TOKEN: ${{ secrets.CRABBOX_COORDINATOR_TOKEN }}
          OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
          OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
          CRABBOX_ACCESS_CLIENT_ID: ${{ secrets.CRABBOX_ACCESS_CLIENT_ID }}
          CRABBOX_ACCESS_CLIENT_SECRET: ${{ secrets.CRABBOX_ACCESS_CLIENT_SECRET }}
          CRABBOX_AWS_REGION: ${{ env.CRABBOX_AWS_REGION }}
          CRABBOX_CAPACITY_REGIONS: ${{ env.CRABBOX_CAPACITY_REGIONS }}
          CRABBOX_LEASE_ID: ${{ needs.resolve_request.outputs.lease_id }}
          CRABBOX_PROVIDER: ${{ needs.resolve_request.outputs.crabbox_provider }}
          SCENARIO_INPUT: ${{ needs.resolve_request.outputs.scenario }}
          CANDIDATE_SHA: ${{ needs.validate_ref.outputs.candidate_revision }}
        shell: bash
        run: |
          set -euo pipefail

          require_var() {
            local key="$1"
            if [[ -z "${!key:-}" ]]; then
              echo "Missing required ${key}." >&2
              exit 1
            fi
          }

          CRABBOX_COORDINATOR="${CRABBOX_COORDINATOR:-${OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR:-}}"
          CRABBOX_COORDINATOR_TOKEN="${CRABBOX_COORDINATOR_TOKEN:-${OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN:-}}"
          export CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN

          require_var OPENAI_API_KEY
          require_var OPENCLAW_QA_CONVEX_SITE_URL
          require_var OPENCLAW_QA_CONVEX_SECRET_CI
          require_var CRABBOX_COORDINATOR_TOKEN

          candidate_repo="$(pwd)/.artifacts/qa-e2e/mantis/telegram-live-worktrees/candidate"
          output_rel=".artifacts/qa-e2e/mantis/telegram-live"
          root="$candidate_repo/$output_rel"
          echo "output_dir=${root}" >> "$GITHUB_OUTPUT"
          model="${OPENCLAW_CI_OPENAI_MODEL:-openai/gpt-5.5}"

          scenario_args=()
          if [[ -n "${SCENARIO_INPUT// }" ]]; then
            IFS=',' read -r -a raw_scenarios <<<"${SCENARIO_INPUT}"
            for raw in "${raw_scenarios[@]}"; do
              scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
              if [[ -n "${scenario}" ]]; then
                scenario_args+=(--scenario "${scenario}")
              fi
            done
          fi

          set +e
          pnpm --dir "$candidate_repo" openclaw qa telegram \
            --repo-root "$candidate_repo" \
            --output-dir "$output_rel" \
            --provider-mode live-frontier \
            --model "$model" \
            --alt-model "$model" \
            --fast \
            --credential-source convex \
            --credential-role ci \
            --allow-failures \
            "${scenario_args[@]}"
          telegram_exit=$?
          set -e

          if [[ ! -f "$root/telegram-qa-summary.json" ]]; then
            echo "Telegram live QA did not produce a summary." >&2
            exit "$telegram_exit"
          fi
          echo "telegram_exit=${telegram_exit}" >> "$GITHUB_OUTPUT"

          node "${GITHUB_WORKSPACE}/scripts/mantis/build-telegram-evidence.mjs" \
            --output-dir "$root" \
            --candidate-ref "$CANDIDATE_SHA" \
            --candidate-sha "$CANDIDATE_SHA" \
            --scenario-label "${SCENARIO_INPUT:-telegram-live}"

          comparison_status="$(jq -r 'if .comparison.pass then "pass" else "fail" end' "$root/mantis-evidence.json")"
          echo "comparison_status=${comparison_status}" >> "$GITHUB_OUTPUT"

          desktop_args=()
          if [[ -n "${CRABBOX_LEASE_ID:-}" ]]; then
            desktop_args+=(--lease-id "$CRABBOX_LEASE_ID")
          fi
          pnpm --dir "$candidate_repo" openclaw qa mantis desktop-browser-smoke \
            --repo-root "$candidate_repo" \
            --html-file "$output_rel/telegram-live-transcript.html" \
            --output-dir "$output_rel/desktop-browser" \
            --provider "$CRABBOX_PROVIDER" \
            --class standard \
            --idle-timeout 45m \
            --ttl 120m \
            --video-duration 18 \
            "${desktop_args[@]}"

          cp "$root/desktop-browser/desktop-browser-smoke.png" "$root/telegram-live-desktop.png"
          if [[ -f "$root/desktop-browser/desktop-browser-smoke.mp4" ]]; then
            cp "$root/desktop-browser/desktop-browser-smoke.mp4" "$root/telegram-live.mp4"
          fi

          if [[ -f "$root/telegram-live.mp4" ]]; then
            if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then
              sudo apt-get update -y >/tmp/mantis-telegram-ffmpeg-apt.log 2>&1 || true
              sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ffmpeg >>/tmp/mantis-telegram-ffmpeg-apt.log 2>&1 || true
            fi
            if ! crabbox media preview \
              --input "$root/telegram-live.mp4" \
              --output "$root/telegram-live-preview.gif" \
              --trimmed-video-output "$root/telegram-live-change.mp4" \
              --json > "$root/telegram-live-preview.json"; then
              rm -f "$root/telegram-live-preview.gif"
              rm -f "$root/telegram-live-change.mp4"
              rm -f "$root/telegram-live-preview.json"
              echo "::warning::Could not generate Telegram motion-trimmed desktop preview."
            fi
          fi

          cat "$root/telegram-qa-report.md" >> "$GITHUB_STEP_SUMMARY"

      - name: Upload Mantis Telegram artifacts
        id: upload_artifact
        if: ${{ always() && steps.run_mantis.outputs.output_dir != '' }}
        uses: actions/upload-artifact@v7
        with:
          name: mantis-telegram-live-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.run_mantis.outputs.output_dir }}
          retention-days: 14
          if-no-files-found: warn

      - name: Create Mantis GitHub App token
        id: mantis_app_token
        if: ${{ always() && needs.resolve_request.outputs.pr_number != '' }}
        uses: actions/create-github-app-token@v3
        with:
          app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
          private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ github.event.repository.name }}
          permission-issues: write
          permission-pull-requests: write

      - name: Comment PR with inline QA evidence
        if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' }}
        env:
          GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
          ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
          MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
          MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
          MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
          MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
          MANTIS_ARTIFACT_R2_REGION: auto
          MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
          REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
          TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
        shell: bash
        run: |
          set -euo pipefail
          root="${{ steps.run_mantis.outputs.output_dir }}"
          if [[ ! -f "$root/mantis-evidence.json" ]]; then
            echo "No Mantis evidence manifest found; skipping PR evidence comment."
            exit 0
          fi
          artifact_url_args=()
          if [[ -n "${ARTIFACT_URL:-}" ]]; then
            artifact_url_args=(--artifact-url "$ARTIFACT_URL")
          fi
          node scripts/mantis/publish-pr-evidence.mjs \
            --manifest "$root/mantis-evidence.json" \
            --target-pr "$TARGET_PR" \
            --artifact-root "mantis/telegram-live/pr-${TARGET_PR}/run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
            --marker "<!-- mantis-telegram-live -->" \
            "${artifact_url_args[@]}" \
            --run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
            --request-source "$REQUEST_SOURCE"

      - name: Fail when Mantis Telegram failed
        if: ${{ always() && steps.run_mantis.outputs.output_dir != '' && (steps.run_mantis.outputs.comparison_status != 'pass' || steps.run_mantis.outputs.telegram_exit != '0') }}
        env:
          COMPARISON_STATUS: ${{ steps.run_mantis.outputs.comparison_status }}
          TELEGRAM_EXIT: ${{ steps.run_mantis.outputs.telegram_exit }}
        run: |
          echo "Mantis Telegram live failed: comparison=${COMPARISON_STATUS:-unset} telegram_exit=${TELEGRAM_EXIT:-unset}." >&2
          exit 1

  clear_issue_comment_reaction:
    name: Clear Mantis command reaction
    needs: [resolve_request, validate_ref, run_telegram_live]
    if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
    runs-on: ubuntu-24.04
    permissions:
      issues: write
    steps:
      - name: Remove workflow eyes reaction
        uses: actions/github-script@v8
        with:
          script: |
            const { owner, repo } = context.repo;
            const commentId = context.payload.comment?.id;
            if (!commentId) {
              core.info("No issue comment id found; skipping reaction cleanup.");
              return;
            }

            const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
              owner,
              repo,
              comment_id: commentId,
              per_page: 100,
            });
            const eyes = reactions.filter(
              (reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
            );
            for (const reaction of eyes) {
              await github.rest.reactions.deleteForIssueComment({
                owner,
                repo,
                comment_id: commentId,
                reaction_id: reaction.id,
              });
              core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
            }
            if (eyes.length === 0) {
              core.info(`No workflow eyes reaction found on comment ${commentId}.`);
            }
