name: Mantis Telegram Desktop Proof

on:
  issue_comment:
    types: [created]
  pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned Mantis label trigger; trusted base workflow validates refs before checkout/use
    types: [labeled]
  workflow_dispatch:
    inputs:
      pr_number:
        description: PR number to capture
        required: true
        type: string
      instructions:
        description: Optional freeform proof instructions for the agent
        required: false
        type: string
      crabbox_provider:
        description: Crabbox provider for the native Telegram Desktop capture
        required: false
        default: aws
        type: choice
        options:
          - aws
          - hetzner
      crabbox_lease_id:
        description: Optional existing Crabbox desktop lease id or slug to reuse
        required: false
        type: string
      publish_artifact_name:
        description: Optional existing proof artifact name to publish without recapturing
        required: false
        type: string
      publish_run_id:
        description: Workflow run id that owns publish_artifact_name; required with publish_artifact_name
        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
  MANTIS_OUTPUT_DIR: .artifacts/qa-e2e/mantis/telegram-desktop-proof

jobs:
  authorize_actor:
    name: Authorize workflow actor
    if: >-
      ${{
        github.event_name == 'workflow_dispatch' ||
        (
          github.event_name == 'pull_request_target' &&
          github.event.action == 'labeled' &&
          github.event.label.name == 'mantis: telegram-visible-proof'
        ) ||
        (
          github.event_name == 'issue_comment' &&
          github.event.issue.pull_request &&
          contains(github.event.issue.labels.*.name, 'mantis: telegram-visible-proof') &&
          (
            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: |
            if (context.eventName === "pull_request_target") {
              core.info(`Accepted Mantis label trigger from ${context.actor}.`);
              core.setOutput("authorized", "true");
              return;
            }

            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:
      baseline_ref: ${{ steps.resolve.outputs.baseline_ref }}
      candidate_ref: ${{ steps.resolve.outputs.candidate_ref }}
      crabbox_provider: ${{ steps.resolve.outputs.crabbox_provider }}
      instructions: ${{ steps.resolve.outputs.instructions }}
      lease_id: ${{ steps.resolve.outputs.lease_id }}
      publish_artifact_name: ${{ steps.resolve.outputs.publish_artifact_name }}
      publish_run_id: ${{ steps.resolve.outputs.publish_run_id }}
      pr_number: ${{ steps.resolve.outputs.pr_number }}
      request_source: ${{ steps.resolve.outputs.request_source }}
      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 ?? ""}`);
            }

            const inputs = context.payload.inputs ?? {};
            const prNumber =
              eventName === "workflow_dispatch"
                ? inputs.pr_number
                : eventName === "pull_request_target"
                  ? String(context.payload.pull_request?.number ?? "")
                  : String(context.payload.issue?.number ?? "");
            if (!prNumber) {
              core.setFailed("Mantis Telegram desktop proof requires a pull request.");
              return;
            }

            const body =
              eventName === "workflow_dispatch"
                ? inputs.instructions || ""
                : eventName === "issue_comment"
                  ? context.payload.comment?.body || ""
                  : "";
            if (eventName === "issue_comment") {
              const normalized = body.toLowerCase();
              const requestedDesktopProof =
                (normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
                (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"));
              if (!requestedDesktopProof) {
                core.notice("Comment mentioned Mantis but did not request Telegram desktop proof.");
                setOutput("should_run", "false");
                setOutput("baseline_ref", "");
                setOutput("candidate_ref", "");
                setOutput("pr_number", "");
                setOutput("instructions", "");
                setOutput("crabbox_provider", "");
                setOutput("lease_id", "");
                setOutput("publish_artifact_name", "");
                setOutput("publish_run_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: Number(prNumber),
            });
            const provider = inputs.crabbox_provider || "aws";
            if (!["aws", "hetzner"].includes(provider)) {
              core.setFailed(`Unsupported Crabbox provider for Mantis Telegram desktop proof: ${provider}`);
              return;
            }

            setOutput("should_run", "true");
            setOutput("baseline_ref", pr.base.sha);
            setOutput("candidate_ref", pr.head.sha);
            setOutput("pr_number", String(pr.number));
            setOutput("instructions", body);
            setOutput("crabbox_provider", provider);
            setOutput("lease_id", inputs.crabbox_lease_id || "");
            setOutput("publish_artifact_name", inputs.publish_artifact_name || "");
            setOutput("publish_run_id", inputs.publish_run_id || "");
            setOutput("request_source", eventName);

            if (eventName === "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_refs:
    name: Validate selected refs
    needs: resolve_request
    if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name == ''
    runs-on: ubuntu-24.04
    outputs:
      baseline_revision: ${{ steps.validate.outputs.baseline_revision }}
      candidate_revision: ${{ steps.validate.outputs.candidate_revision }}
      candidate_trust: ${{ steps.validate.outputs.candidate_trust }}
    steps:
      - name: Checkout harness ref
        uses: actions/checkout@v6
        with:
          ref: main
          persist-credentials: false
          fetch-depth: 0

      - name: Validate refs are trusted
        id: validate
        env:
          BASELINE_REF: ${{ needs.resolve_request.outputs.baseline_ref }}
          CANDIDATE_REF: ${{ needs.resolve_request.outputs.candidate_ref }}
          GH_TOKEN: ${{ github.token }}
          PR_NUMBER: ${{ needs.resolve_request.outputs.pr_number }}
        shell: bash
        run: |
          set -euo pipefail

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

          resolve_commit() {
            local input_ref="$2"
            local revision=""

            if ! revision="$(git rev-parse --verify "${input_ref}^{commit}" 2>/dev/null)"; then
              echo "$1 ref '${input_ref}' is not available in the workflow checkout." >&2
              exit 1
            fi
            printf '%s\n' "$revision"
          }

          baseline_revision="$(resolve_commit baseline "$BASELINE_REF")"
          if ! git merge-base --is-ancestor "$baseline_revision" refs/remotes/origin/main; then
            echo "baseline ref '${BASELINE_REF}' resolved to ${baseline_revision}, which is not on main." >&2
            exit 1
          fi
          pr_head="$(
            gh api \
              -H "Accept: application/vnd.github+json" \
              "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
              --jq '{state, head_sha: .head.sha, head_repo: .head.repo.full_name}'
          )"
          pr_state="$(jq -r '.state' <<<"$pr_head")"
          pr_head_sha="$(jq -r '.head_sha' <<<"$pr_head")"
          pr_head_repo="$(jq -r '.head_repo' <<<"$pr_head")"
          candidate_revision="$CANDIDATE_REF"
          if [[ ! "$candidate_revision" =~ ^[0-9a-f]{40}$ ]]; then
            echo "candidate ref '${CANDIDATE_REF}' is not an immutable commit SHA." >&2
            exit 1
          fi
          if [[ "$pr_state" != "open" || "$candidate_revision" != "$pr_head_sha" ]]; then
            echo "candidate ref '${CANDIDATE_REF}' resolved to ${candidate_revision}, which is not the open PR head." >&2
            exit 1
          fi
          candidate_trust="open-pr-head"
          if [[ "$pr_head_repo" != "$GITHUB_REPOSITORY" ]]; then
            candidate_trust="fork-pr-head"
          fi

          echo "baseline_revision=${baseline_revision}" >> "$GITHUB_OUTPUT"
          echo "candidate_revision=${candidate_revision}" >> "$GITHUB_OUTPUT"
          echo "candidate_trust=${candidate_trust}" >> "$GITHUB_OUTPUT"
          {
            echo "baseline: \`${BASELINE_REF}\`"
            echo "baseline SHA: \`${baseline_revision}\`"
            echo "baseline trust: \`main-ancestor\`"
            echo "candidate: \`${CANDIDATE_REF}\`"
            echo "candidate SHA: \`${candidate_revision}\`"
            echo "candidate trust: \`${candidate_trust}\`"
          } >> "$GITHUB_STEP_SUMMARY"

  run_telegram_desktop_proof:
    name: Run agentic native Telegram proof
    needs: [resolve_request, validate_refs]
    if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name == ''
    runs-on: blacksmith-16vcpu-ubuntu-2404
    timeout-minutes: 360
    environment: qa-live-shared
    outputs:
      comparison_status: ${{ steps.inspect.outputs.comparison_status }}
      output_dir: ${{ steps.inspect.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: 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"
          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 "$install_dir/crabbox" ./cmd/crabbox
          sudo install -m 0755 "$install_dir/crabbox" /usr/local/bin/crabbox
          crabbox --version
          crabbox media preview --help >/dev/null

      - name: Install local proof tools
        shell: bash
        run: |
          set -euo pipefail
          test -f scripts/e2e/telegram-user-driver.py
          cat >"${RUNNER_TEMP}/openclaw-telegram-user-crabbox-proof" <<'EOF'
          #!/usr/bin/env bash
          set -euo pipefail
          exec node --import tsx "${GITHUB_WORKSPACE}/scripts/e2e/telegram-user-crabbox-proof.ts" "$@"
          EOF
          chmod 0755 "${RUNNER_TEMP}/openclaw-telegram-user-crabbox-proof"
          sudo install -m 0755 "${RUNNER_TEMP}/openclaw-telegram-user-crabbox-proof" /usr/local/bin/openclaw-telegram-user-crabbox-proof
          /usr/local/bin/openclaw-telegram-user-crabbox-proof --help >/dev/null
          media_tools="${RUNNER_TEMP}/mantis-media-tools"
          install -d "$media_tools"
          curl --fail --location --retry 3 --retry-delay 2 \
            --connect-timeout 15 --max-time 180 \
            https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz \
            --output "$media_tools/ffmpeg.tar.xz"
          tar -xJf "$media_tools/ffmpeg.tar.xz" -C "$media_tools"
          bin_dir="$(find "$media_tools" -type d -path '*/bin' | head -n 1)"
          sudo install -m 0755 "$bin_dir/ffmpeg" /usr/local/bin/ffmpeg
          sudo install -m 0755 "$bin_dir/ffprobe" /usr/local/bin/ffprobe
          ffmpeg -version >/dev/null
          ffprobe -version >/dev/null

      - name: Ensure agent key exists
        env:
          OPENAI_API_KEY: ${{ secrets.OPENCLAW_MANTIS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
        run: |
          set -euo pipefail
          if [ -z "${OPENAI_API_KEY:-}" ]; then
            echo "Missing OPENCLAW_MANTIS_AGENT_OPENAI_API_KEY or OPENAI_API_KEY secret." >&2
            exit 1
          fi

      - name: Prepare Codex user
        shell: bash
        run: |
          set -euo pipefail
          sudo useradd --create-home --shell /bin/bash codex
          {
            printf '%s\n' 'Defaults env_keep += "CODEX_HOME CODEX_INTERNAL_ORIGINATOR_OVERRIDE"'
            printf '%s\n' 'Defaults env_keep += "BASELINE_REF BASELINE_SHA CANDIDATE_REF CANDIDATE_SHA"'
            printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_AWS_REGION CRABBOX_CAPACITY_REGIONS CRABBOX_LEASE_ID CRABBOX_PROVIDER"'
            printf '%s\n' 'Defaults env_keep += "GH_TOKEN MANTIS_CANDIDATE_TRUST MANTIS_INSTRUCTIONS MANTIS_OUTPUT_DIR MANTIS_PR_NUMBER"'
            printf '%s\n' 'Defaults env_keep += "OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_QA_CONVEX_SECRET_CI OPENCLAW_QA_CONVEX_SITE_URL OPENCLAW_QA_CREDENTIAL_OWNER_ID OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN"'
            printf '%s\n' 'Defaults env_keep += "OPENCLAW_TELEGRAM_USER_CRABBOX_BIN OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT OPENCLAW_TELEGRAM_USER_PROOF_CMD"'
          } | sudo tee /etc/sudoers.d/mantis-codex-env >/dev/null
          sudo chmod 0440 /etc/sudoers.d/mantis-codex-env
          codex_home="/tmp/mantis-codex-home-${GITHUB_RUN_ID}"
          sudo install -d -m 0770 -o codex -g codex "$codex_home"
          sudo setfacl -m u:runner:rwx,u:codex:rwx "$codex_home"
          sudo setfacl -d -m u:runner:rwx,u:codex:rwx "$codex_home"
          workspace_parent="$(dirname "$GITHUB_WORKSPACE")"
          while [ "$workspace_parent" != "/" ]; do
            sudo setfacl -m u:codex:--x "$workspace_parent"
            [ "$workspace_parent" = "/home/runner" ] && break
            workspace_parent="$(dirname "$workspace_parent")"
          done
          sudo chown -R codex:codex "$GITHUB_WORKSPACE"

      - name: Run Codex Mantis Telegram agent
        uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
        env:
          BASELINE_REF: ${{ needs.resolve_request.outputs.baseline_ref }}
          BASELINE_SHA: ${{ needs.validate_refs.outputs.baseline_revision }}
          CANDIDATE_REF: ${{ needs.resolve_request.outputs.candidate_ref }}
          CANDIDATE_SHA: ${{ needs.validate_refs.outputs.candidate_revision }}
          CRABBOX_ACCESS_CLIENT_ID: ${{ secrets.CRABBOX_ACCESS_CLIENT_ID }}
          CRABBOX_ACCESS_CLIENT_SECRET: ${{ secrets.CRABBOX_ACCESS_CLIENT_SECRET }}
          CRABBOX_COORDINATOR: ${{ secrets.CRABBOX_COORDINATOR || secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
          CRABBOX_COORDINATOR_TOKEN: ${{ secrets.CRABBOX_COORDINATOR_TOKEN || secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
          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 }}
          GH_TOKEN: ${{ github.token }}
          MANTIS_CANDIDATE_TRUST: ${{ needs.validate_refs.outputs.candidate_trust }}
          MANTIS_INSTRUCTIONS: ${{ needs.resolve_request.outputs.instructions }}
          MANTIS_OUTPUT_DIR: ${{ env.MANTIS_OUTPUT_DIR }}
          MANTIS_PR_NUMBER: ${{ needs.resolve_request.outputs.pr_number }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
          OPENCLAW_QA_CREDENTIAL_OWNER_ID: mantis-telegram-desktop-${{ github.run_id }}-${{ github.run_attempt }}
          OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
          OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
          OPENCLAW_TELEGRAM_USER_CRABBOX_BIN: /usr/local/bin/crabbox
          OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER: ${{ needs.resolve_request.outputs.crabbox_provider }}
          OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT: ${{ github.workspace }}/scripts/e2e/telegram-user-driver.py
          OPENCLAW_TELEGRAM_USER_PROOF_CMD: /usr/local/bin/openclaw-telegram-user-crabbox-proof
        with:
          openai-api-key: ${{ secrets.OPENCLAW_MANTIS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
          prompt-file: .github/codex/prompts/mantis-telegram-desktop-proof.md
          model: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
          effort: medium
          sandbox: danger-full-access
          codex-args: '["-c","service_tier=\"fast\""]'
          codex-home: /tmp/mantis-codex-home-${{ github.run_id }}
          safety-strategy: unprivileged-user
          codex-user: codex
          allow-bot-users: clawsweeper[bot]

      - name: Release leaked Telegram proof leases
        if: ${{ always() }}
        env:
          CRABBOX_PROVIDER: ${{ needs.resolve_request.outputs.crabbox_provider }}
          OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
          OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
        shell: bash
        run: |
          set -euo pipefail
          if [[ ! -d .artifacts/qa-e2e ]]; then
            exit 0
          fi
          status=0
          mapfile -d '' session_files < <(sudo find .artifacts/qa-e2e -name session.json -type f -print0)
          for session_file in "${session_files[@]}"; do
            if ! sudo -u codex node -e 'const fs = require("fs"); const session = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.exit(session.command === "telegram-user-crabbox-session" ? 0 : 1);' "$session_file"; then
              continue
            fi
            lease_file="${session_file%/session.json}/.session/lease.json"
            if [[ ! -f "$lease_file" ]]; then
              continue
            fi
            if ! sudo -u codex env \
              OPENCLAW_QA_CONVEX_SECRET_CI="$OPENCLAW_QA_CONVEX_SECRET_CI" \
              OPENCLAW_QA_CONVEX_SITE_URL="$OPENCLAW_QA_CONVEX_SITE_URL" \
              OPENCLAW_TELEGRAM_USER_CRABBOX_BIN=/usr/local/bin/crabbox \
              OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER="$CRABBOX_PROVIDER" \
              node --import tsx "$GITHUB_WORKSPACE/scripts/e2e/telegram-user-crabbox-proof.ts" \
                finish --session "$session_file" --preview-crop telegram-window; then
              status=1
            fi
          done
          mapfile -d '' lease_files < <(sudo find .artifacts/qa-e2e -path '*/.session/lease.json' -type f -print0)
          for lease_file in "${lease_files[@]}"; do
            if ! sudo -u codex node -e 'const fs = require("fs"); const lease = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.exit(lease.kind === "telegram-user" ? 0 : 1);' "$lease_file"; then
              continue
            fi
            if ! sudo -u codex env \
              OPENCLAW_QA_CONVEX_SECRET_CI="$OPENCLAW_QA_CONVEX_SECRET_CI" \
              OPENCLAW_QA_CONVEX_SITE_URL="$OPENCLAW_QA_CONVEX_SITE_URL" \
              node --import tsx "$GITHUB_WORKSPACE/scripts/e2e/telegram-user-credential.ts" \
                release --lease-file "$lease_file"; then
              status=1
            fi
          done
          exit "$status"

      - name: Inspect Mantis evidence manifest
        id: inspect
        if: ${{ always() }}
        shell: bash
        run: |
          set -euo pipefail
          output_dir="$MANTIS_OUTPUT_DIR"
          echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
          manifest="$output_dir/mantis-evidence.json"
          if [[ ! -f "$manifest" ]]; then
            echo "Mantis agent did not produce ${manifest}." >&2
            exit 1
          fi
          comparison_status="$(jq -r 'if .comparison.pass then "pass" else "fail" end' "$manifest")"
          echo "comparison_status=${comparison_status}" >> "$GITHUB_OUTPUT"

      - name: Upload Mantis Telegram desktop artifacts
        id: upload_artifact
        if: ${{ always() && steps.inspect.outputs.output_dir != '' }}
        uses: actions/upload-artifact@v7
        with:
          name: mantis-telegram-desktop-proof-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ steps.inspect.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.inspect.outputs.output_dir != '' }}
        env:
          ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
          GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
          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.inspect.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-desktop/pr-${TARGET_PR}/run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
            --marker "<!-- mantis-telegram-desktop-proof -->" \
            "${artifact_url_args[@]}" \
            --run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
            --request-source "$REQUEST_SOURCE"

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

  publish_existing_telegram_desktop_proof:
    name: Publish existing native Telegram proof
    needs: resolve_request
    if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name != ''
    runs-on: ubuntu-24.04
    environment: qa-live-shared
    steps:
      - name: Checkout harness ref
        uses: actions/checkout@v6
        with:
          persist-credentials: false

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

      - name: Download existing proof artifact
        env:
          GH_TOKEN: ${{ github.token }}
          PUBLISH_ARTIFACT_NAME: ${{ needs.resolve_request.outputs.publish_artifact_name }}
          PUBLISH_RUN_ID: ${{ needs.resolve_request.outputs.publish_run_id }}
        shell: bash
        run: |
          set -euo pipefail
          if [[ -z "${PUBLISH_RUN_ID:-}" ]]; then
            echo "publish_run_id is required when publish_artifact_name is set." >&2
            exit 1
          fi
          run_id="$PUBLISH_RUN_ID"
          gh run download "$run_id" \
            --repo "$GITHUB_REPOSITORY" \
            --name "$PUBLISH_ARTIFACT_NAME" \
            --dir "$MANTIS_OUTPUT_DIR"

          artifacts_json="$(
            gh api \
              -H "Accept: application/vnd.github+json" \
              "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts"
          )"
          artifact_id="$(jq -r --arg name "$PUBLISH_ARTIFACT_NAME" '.artifacts[] | select(.name == $name) | .id' <<<"$artifacts_json" | head -n 1)"
          if [[ -z "$artifact_id" || "$artifact_id" == "null" ]]; then
            echo "Could not resolve artifact id for '${PUBLISH_ARTIFACT_NAME}' in run ${run_id}." >&2
            exit 1
          fi
          echo "PUBLISH_RUN_ID=${run_id}" >> "$GITHUB_ENV"
          echo "PUBLISH_ARTIFACT_URL=https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts/${artifact_id}" >> "$GITHUB_ENV"

      - name: Create Mantis GitHub App token
        id: mantis_app_token
        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
        env:
          GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
          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="$MANTIS_OUTPUT_DIR"
          if [[ ! -f "$root/mantis-evidence.json" ]]; then
            echo "Downloaded artifact does not contain ${root}/mantis-evidence.json." >&2
            exit 1
          fi
          node scripts/mantis/publish-pr-evidence.mjs \
            --manifest "$root/mantis-evidence.json" \
            --target-pr "$TARGET_PR" \
            --artifact-root "mantis/telegram-desktop/pr-${TARGET_PR}/published-${PUBLISH_RUN_ID}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
            --marker "<!-- mantis-telegram-desktop-proof -->" \
            --artifact-url "$PUBLISH_ARTIFACT_URL" \
            --run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${PUBLISH_RUN_ID}" \
            --request-source "$REQUEST_SOURCE"

  clear_issue_comment_reaction:
    name: Clear Mantis command reaction
    needs: [resolve_request, validate_refs, run_telegram_desktop_proof]
    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}.`);
            }
