mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
614e084cf8
Queue Telegram-visible Mantis proofs at workflow level so only one shared-user run allocates a runner at a time. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
420 lines
20 KiB
YAML
420 lines
20 KiB
YAML
name: Mantis Telegram Desktop Proof
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr_number:
|
|
description: Pull request number to prove
|
|
required: true
|
|
type: string
|
|
instructions:
|
|
description: Optional maintainer guidance for the open-ended investigation
|
|
required: false
|
|
type: string
|
|
allow_fork_candidate:
|
|
description: Allow this secret-bearing run for the selected fork PR head
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
approved_head_sha:
|
|
description: Exact fork PR head SHA approved for this secret-bearing run
|
|
required: false
|
|
type: string
|
|
request_source:
|
|
description: Dispatcher request source; ignored for manual runs
|
|
required: false
|
|
default: workflow_dispatch
|
|
type: string
|
|
|
|
concurrency:
|
|
group: mantis-telegram-visible-proof
|
|
cancel-in-progress: false
|
|
queue: max
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
env:
|
|
CRABBOX_LINUX_AMD64_SHA256: c9d38e67af31e5383ab4117bae9b88a71a04c80da5d923f851fbd731ece3a3a4
|
|
CRABBOX_VERSION: 0.45.0
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
MANTIS_OUTPUT_DIR: .artifacts/qa-e2e/mantis/telegram-visible-proof
|
|
NODE_VERSION: "24.x"
|
|
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
|
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
|
|
|
jobs:
|
|
resolve_request:
|
|
name: Resolve Mantis request
|
|
runs-on: ubuntu-24.04
|
|
environment: qa-live-shared
|
|
outputs:
|
|
baseline_revision: ${{ steps.resolve.outputs.baseline_revision }}
|
|
head_revision: ${{ steps.resolve.outputs.head_revision }}
|
|
instructions: ${{ steps.resolve.outputs.instructions }}
|
|
merge_base_revision: ${{ steps.resolve.outputs.merge_base_revision }}
|
|
pr_context: ${{ steps.resolve.outputs.pr_context }}
|
|
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 inputs = context.payload.inputs ?? {};
|
|
const prNumber = Number(inputs.pr_number);
|
|
if (!Number.isSafeInteger(prNumber) || prNumber < 1) {
|
|
core.setFailed("Mantis requires a valid pull request number.");
|
|
return;
|
|
}
|
|
const { owner, repo } = context.repo;
|
|
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
if (pr.state !== "open" || !pr.head.repo) {
|
|
core.setFailed("Mantis requires an open pull request with an available head repository.");
|
|
return;
|
|
}
|
|
if (pr.base.ref !== "main") {
|
|
core.setFailed(`Mantis proves landing on main; this PR targets '${pr.base.ref}'.`);
|
|
return;
|
|
}
|
|
const immutableSha = /^[0-9a-f]{40}$/u;
|
|
const headRevision = pr.head.sha;
|
|
if (!immutableSha.test(headRevision)) {
|
|
core.setFailed(`PR head '${headRevision}' is not an immutable commit SHA.`);
|
|
return;
|
|
}
|
|
const { data: mainRef } = await github.rest.git.getRef({ owner, repo, ref: "heads/main" });
|
|
const baselineRevision = mainRef.object.sha;
|
|
const comparison = await github.request("GET /repos/{owner}/{repo}/compare/{basehead}", {
|
|
owner,
|
|
repo,
|
|
basehead: `${baselineRevision}...${headRevision}`,
|
|
});
|
|
const mergeBaseRevision = comparison.data.merge_base_commit.sha;
|
|
if (!immutableSha.test(baselineRevision) || !immutableSha.test(mergeBaseRevision)) {
|
|
core.setFailed("Mantis could not resolve immutable main and merge-base revisions.");
|
|
return;
|
|
}
|
|
if (pr.head.repo.full_name !== `${owner}/${repo}`) {
|
|
const allowFork = inputs.allow_fork_candidate === true || inputs.allow_fork_candidate === "true";
|
|
if (!allowFork || inputs.approved_head_sha !== headRevision) {
|
|
core.setFailed(`Fork approval must name the exact current PR head SHA (${headRevision}).`);
|
|
return;
|
|
}
|
|
}
|
|
const dispatcherSources = new Set(["clawsweeper_label", "issue_comment"]);
|
|
const requestSource =
|
|
context.actor === "github-actions[bot]" && dispatcherSources.has(inputs.request_source)
|
|
? inputs.request_source
|
|
: "workflow_dispatch";
|
|
core.setOutput("baseline_revision", baselineRevision);
|
|
core.setOutput("head_revision", headRevision);
|
|
core.setOutput("merge_base_revision", mergeBaseRevision);
|
|
core.setOutput("pr_number", String(pr.number));
|
|
core.setOutput("instructions", inputs.instructions || "");
|
|
core.setOutput(
|
|
"pr_context",
|
|
JSON.stringify({ title: pr.title.slice(0, 500), body: (pr.body ?? "").slice(0, 12000) }),
|
|
);
|
|
core.setOutput("request_source", requestSource);
|
|
core.setOutput("should_run", "true");
|
|
|
|
- name: Create Mantis status token
|
|
id: status_token
|
|
if: ${{ steps.resolve.outputs.pr_number != '' }}
|
|
continue-on-error: true
|
|
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: Report Mantis run started
|
|
if: ${{ steps.status_token.outcome == 'success' }}
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
|
env:
|
|
TARGET_PR: ${{ steps.resolve.outputs.pr_number }}
|
|
with:
|
|
github-token: ${{ steps.status_token.outputs.token }}
|
|
script: |
|
|
const marker = `<!-- mantis-telegram-visible-proof:${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT} -->`;
|
|
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
const { owner, repo } = context.repo;
|
|
const issue_number = Number(process.env.TARGET_PR);
|
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
owner, repo, issue_number, per_page: 100,
|
|
});
|
|
const previous = comments.filter(
|
|
(comment) =>
|
|
comment.user?.login === "openclaw-mantis[bot]" &&
|
|
(comment.body?.includes("<!-- mantis-telegram-visible-proof:") ||
|
|
comment.body?.includes("<!-- mantis-telegram-desktop-proof:")),
|
|
);
|
|
for (const stale of previous) {
|
|
await github.rest.issues.deleteComment({ owner, repo, comment_id: stale.id });
|
|
}
|
|
await github.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
body: `${marker}\n👀 Mantis is running an open-ended real-user Telegram investigation. [Follow the run](${runUrl}).`,
|
|
});
|
|
|
|
run_telegram_visible_proof:
|
|
name: Run agentic native Telegram proof
|
|
needs: resolve_request
|
|
if: needs.resolve_request.outputs.should_run == 'true'
|
|
runs-on: blacksmith-16vcpu-ubuntu-2404
|
|
timeout-minutes: 100
|
|
environment: qa-live-shared
|
|
outputs:
|
|
comparison_status: ${{ steps.collect.outputs.comparison_status }}
|
|
output_dir: ${{ steps.collect.outputs.output_dir }}
|
|
steps:
|
|
- name: Checkout harness ref
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ github.workflow_sha }}
|
|
persist-credentials: false
|
|
fetch-depth: 1
|
|
|
|
- name: Setup Node environment
|
|
id: setup-node-env
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
cache-mode: read-write
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Setup uv for Telegram user driver
|
|
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
|
|
- name: Install trusted Telegram proof tools
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-install-tools.sh
|
|
|
|
- name: Install Crabbox CLI
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-install-crabbox.sh
|
|
|
|
- name: Build local Telegram Desktop image
|
|
run: bash scripts/mantis/build-telegram-desktop-image.sh
|
|
|
|
- name: Create exact proof worktrees
|
|
id: worktrees
|
|
env:
|
|
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
|
|
HEAD_SHA: ${{ needs.resolve_request.outputs.head_revision }}
|
|
MERGE_BASE_SHA: ${{ needs.resolve_request.outputs.merge_base_revision }}
|
|
PR_NUMBER: ${{ needs.resolve_request.outputs.pr_number }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-create-worktrees.sh
|
|
|
|
- name: Restore exact baseline build
|
|
id: baseline_cache
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: .artifacts/mantis-runtime-build.tar
|
|
key: ${{ runner.os }}-${{ runner.arch }}-mantis-runtime-v2-${{ steps.worktrees.outputs.lockfile_sha256 }}-${{ steps.worktrees.outputs.node_version }}-${{ steps.worktrees.outputs.pnpm_version }}-${{ needs.resolve_request.outputs.baseline_revision }}
|
|
|
|
- name: Build baseline and candidate
|
|
env:
|
|
BASELINE_ROOT: ${{ steps.worktrees.outputs.baseline_root }}
|
|
CANDIDATE_ROOT: ${{ steps.worktrees.outputs.candidate_root }}
|
|
BASELINE_ARCHIVE: ${{ github.workspace }}/.artifacts/mantis-runtime-build.tar
|
|
BASELINE_CACHE_HIT: ${{ steps.baseline_cache.outputs.cache-hit }}
|
|
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
|
|
CANDIDATE_SHA: ${{ steps.worktrees.outputs.candidate_revision }}
|
|
HOST_PNPM_STORE: ${{ steps.setup-node-env.outputs.pnpm-store-cache-path }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-build-worktrees.sh
|
|
|
|
- name: Save exact baseline build
|
|
if: steps.setup-node-env.outputs.cache-mode == 'read-write' && steps.baseline_cache.outputs.cache-hit != 'true'
|
|
continue-on-error: true
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: .artifacts/mantis-runtime-build.tar
|
|
key: ${{ steps.baseline_cache.outputs.cache-primary-key }}
|
|
|
|
- name: Install TDLib and restore Telegram QA user
|
|
id: telegram_credential
|
|
env:
|
|
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-visible-${{ github.run_id }}-${{ github.run_attempt }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-lease-user.sh
|
|
|
|
- name: Prepare Codex user and bound Telegram bridges
|
|
env:
|
|
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
|
|
BASELINE_ROOT: ${{ steps.worktrees.outputs.baseline_root }}
|
|
CANDIDATE_ROOT: ${{ steps.worktrees.outputs.candidate_root }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-prepare-codex.sh
|
|
|
|
- name: Prepare Codex action runtime
|
|
uses: openai/codex-action@52fe01ec70a42f454c9d2ebd47598f9fd6893d56
|
|
with:
|
|
openai-api-key: ${{ secrets.OPENCLAW_MANTIS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
|
codex-home: /tmp/mantis-codex-home-${{ github.run_id }}
|
|
safety-strategy: unprivileged-user
|
|
codex-user: codex
|
|
allow-bot-users: github-actions[bot]
|
|
|
|
- name: Run open-ended Telegram investigation with GPT-5.6
|
|
env:
|
|
LEASE_LOST_MARKER: ${{ steps.telegram_credential.outputs.lease_lost_marker }}
|
|
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
|
|
CANDIDATE_SHA: ${{ steps.worktrees.outputs.candidate_revision }}
|
|
CODEX_HOME: /tmp/mantis-codex-home-${{ github.run_id }}
|
|
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: codex_github_action
|
|
GITHUB_WORKSPACE: ${{ github.workspace }}
|
|
MANTIS_BASELINE_ROOT: ${{ steps.worktrees.outputs.baseline_root }}
|
|
MANTIS_CANDIDATE_ROOT: ${{ steps.worktrees.outputs.candidate_root }}
|
|
MANTIS_BASELINE: /usr/local/bin/mantis-telegram-baseline
|
|
MANTIS_CANDIDATE: /usr/local/bin/mantis-telegram-candidate
|
|
MANTIS_FIXTURE_BASELINE: ${{ steps.telegram_credential.outputs.session_root }}/fixture-plugins/baseline
|
|
MANTIS_FIXTURE_CANDIDATE: ${{ steps.telegram_credential.outputs.session_root }}/fixture-plugins/candidate
|
|
MANTIS_INSTRUCTIONS: ${{ needs.resolve_request.outputs.instructions }}
|
|
MANTIS_PR_CONTEXT: ${{ needs.resolve_request.outputs.pr_context }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-run-agent.sh
|
|
|
|
- name: Clean up Mantis sessions
|
|
id: cleanup
|
|
if: ${{ always() }}
|
|
env:
|
|
LEASE_KEEPALIVE_PID_FILE: ${{ steps.telegram_credential.outputs.lease_keepalive_pid_file }}
|
|
LEASE_FILE: ${{ steps.telegram_credential.outputs.lease_file }}
|
|
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-cleanup-proof.sh
|
|
|
|
- name: Collect trusted Telegram evidence
|
|
id: collect
|
|
if: ${{ success() && steps.cleanup.outputs.safe_to_release == 'true' }}
|
|
env:
|
|
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
|
|
BASELINE_SHA: ${{ needs.resolve_request.outputs.baseline_revision }}
|
|
CANDIDATE_SHA: ${{ steps.worktrees.outputs.candidate_revision }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-collect-proof.sh
|
|
|
|
- name: Release Telegram QA user lease
|
|
id: release
|
|
if: ${{ always() && steps.cleanup.outputs.safe_to_release == 'true' }}
|
|
env:
|
|
LEASE_FILE: ${{ steps.telegram_credential.outputs.lease_file }}
|
|
LEASE_LOST_MARKER: ${{ steps.telegram_credential.outputs.lease_lost_marker }}
|
|
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: bash scripts/mantis/telegram-visible-release-user.sh
|
|
|
|
- name: Remove private Mantis runtime state
|
|
if: ${{ always() && steps.cleanup.outputs.safe_to_release == 'true' }}
|
|
env:
|
|
SESSION_ROOT: ${{ steps.telegram_credential.outputs.session_root }}
|
|
SUT_CREDENTIAL_DIR: ${{ steps.telegram_credential.outputs.sut_credential_dir }}
|
|
CREDENTIAL_DIR: ${{ steps.telegram_credential.outputs.credential_dir }}
|
|
shell: bash
|
|
run: bash scripts/mantis/telegram-visible-remove-private.sh
|
|
|
|
- name: Upload Mantis Telegram artifacts
|
|
id: upload
|
|
if: ${{ always() && steps.collect.outputs.output_dir != '' }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: mantis-telegram-visible-proof-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: ${{ steps.collect.outputs.output_dir }}
|
|
retention-days: 14
|
|
if-no-files-found: error
|
|
|
|
- name: Create Mantis GitHub App token
|
|
id: 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: read
|
|
|
|
- name: Publish Telegram proof
|
|
id: publish
|
|
if: ${{ always() && steps.collect.outputs.output_dir != '' && steps.app_token.outcome == 'success' }}
|
|
env:
|
|
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
|
|
GH_TOKEN: ${{ steps.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
|
|
marker="<!-- mantis-telegram-visible-proof:${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT} -->"
|
|
node scripts/mantis/publish-pr-evidence.mjs \
|
|
--manifest "${{ steps.collect.outputs.output_dir }}/mantis-evidence.json" \
|
|
--target-pr "$TARGET_PR" \
|
|
--artifact-root "mantis/telegram-visible/pr-${TARGET_PR}/run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
|
|
--marker "$marker" \
|
|
--create-missing false \
|
|
--artifact-url "$ARTIFACT_URL" \
|
|
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
|
--request-source "$REQUEST_SOURCE"
|
|
|
|
- name: Report incomplete Mantis proof
|
|
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.publish.outcome != 'success' }}
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
|
env:
|
|
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
|
|
with:
|
|
github-token: ${{ steps.app_token.outputs.token || github.token }}
|
|
script: |
|
|
const marker = `<!-- mantis-telegram-visible-proof:${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT} -->`;
|
|
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
const { owner, repo } = context.repo;
|
|
const issue_number = Number(process.env.TARGET_PR);
|
|
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100 });
|
|
const existing = comments.find((comment) => comment.body?.includes(marker));
|
|
const body = `${marker}\nMantis could not complete this proof. [Open the failed run](${runUrl}).`;
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
|
|
} else {
|
|
await github.rest.issues.createComment({ owner, repo, issue_number, body });
|
|
}
|
|
|
|
- name: Fail on proof or cleanup failure
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
(
|
|
steps.collect.outputs.comparison_status != 'pass' ||
|
|
steps.cleanup.outcome == 'failure' ||
|
|
steps.release.outcome == 'failure' ||
|
|
steps.publish.outcome == 'failure'
|
|
)
|
|
}}
|
|
env:
|
|
COMPARISON_STATUS: ${{ steps.collect.outputs.comparison_status }}
|
|
run: |
|
|
echo "Mantis Telegram visible proof failed: ${COMPARISON_STATUS:-no trusted verdict}." >&2
|
|
exit 1
|