mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-15 07:04:01 -06:00
381cec0051
Require live Mantis and Telegram proof artifact uploads to fail when evidence is missing and guard the workflow invariant.
743 lines
34 KiB
YAML
743 lines
34 KiB
YAML
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
|
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
|
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # 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@e0fdf01220eb9a88167c4898839d273e3f2609d1
|
|
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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 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: error
|
|
|
|
- name: Create Mantis GitHub App token
|
|
id: mantis_app_token
|
|
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' }}
|
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # 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@bcd2ba49218906704ab6c1aa796996da409d3eb1 # 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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
|
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}.`);
|
|
}
|