Skip to content

Commit 59efd95

Browse files
committed
ci(mantis): add telegram proof label trigger
1 parent b764396 commit 59efd95

2 files changed

Lines changed: 176 additions & 2 deletions

File tree

.github/workflows/mantis-telegram-desktop-proof.yml

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ name: Mantis Telegram Desktop Proof
33
on:
44
issue_comment:
55
types: [created]
6+
pull_request_target:
7+
types: [labeled]
68
workflow_dispatch:
79
inputs:
810
pr_number:
@@ -25,6 +27,14 @@ on:
2527
description: Optional existing Crabbox desktop lease id or slug to reuse
2628
required: false
2729
type: string
30+
publish_artifact_name:
31+
description: Optional existing proof artifact name to publish without recapturing
32+
required: false
33+
type: string
34+
publish_run_id:
35+
description: Workflow run id that owns publish_artifact_name; required with publish_artifact_name
36+
required: false
37+
type: string
2838

2939
permissions:
3040
actions: read
@@ -47,6 +57,11 @@ jobs:
4757
if: >-
4858
${{
4959
github.event_name == 'workflow_dispatch' ||
60+
(
61+
github.event_name == 'pull_request_target' &&
62+
github.event.action == 'labeled' &&
63+
github.event.label.name == 'mantis: telegram-visible-proof'
64+
) ||
5065
(
5166
github.event_name == 'issue_comment' &&
5267
github.event.issue.pull_request &&
@@ -66,6 +81,12 @@ jobs:
6681
uses: actions/github-script@v8
6782
with:
6883
script: |
84+
if (context.eventName === "pull_request_target") {
85+
core.info(`Accepted Mantis label trigger from ${context.actor}.`);
86+
core.setOutput("authorized", "true");
87+
return;
88+
}
89+
6990
const allowed = new Set(["admin", "maintain", "write"]);
7091
const { owner, repo } = context.repo;
7192
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
@@ -95,6 +116,8 @@ jobs:
95116
crabbox_provider: ${{ steps.resolve.outputs.crabbox_provider }}
96117
instructions: ${{ steps.resolve.outputs.instructions }}
97118
lease_id: ${{ steps.resolve.outputs.lease_id }}
119+
publish_artifact_name: ${{ steps.resolve.outputs.publish_artifact_name }}
120+
publish_run_id: ${{ steps.resolve.outputs.publish_run_id }}
98121
pr_number: ${{ steps.resolve.outputs.pr_number }}
99122
request_source: ${{ steps.resolve.outputs.request_source }}
100123
steps:
@@ -112,7 +135,11 @@ jobs:
112135
113136
const inputs = context.payload.inputs ?? {};
114137
const prNumber =
115-
eventName === "workflow_dispatch" ? inputs.pr_number : String(context.payload.issue?.number ?? "");
138+
eventName === "workflow_dispatch"
139+
? inputs.pr_number
140+
: eventName === "pull_request_target"
141+
? String(context.payload.pull_request?.number ?? "")
142+
: String(context.payload.issue?.number ?? "");
116143
if (!prNumber) {
117144
core.setFailed("Mantis Telegram desktop proof requires a pull request.");
118145
return;
@@ -124,7 +151,12 @@ jobs:
124151
repo,
125152
pull_number: Number(prNumber),
126153
});
127-
const body = eventName === "workflow_dispatch" ? inputs.instructions || "" : context.payload.comment?.body || "";
154+
const body =
155+
eventName === "workflow_dispatch"
156+
? inputs.instructions || ""
157+
: eventName === "issue_comment"
158+
? context.payload.comment?.body || ""
159+
: "";
128160
const provider = inputs.crabbox_provider || "aws";
129161
if (!["aws", "hetzner"].includes(provider)) {
130162
core.setFailed(`Unsupported Crabbox provider for Mantis Telegram desktop proof: ${provider}`);
@@ -137,6 +169,8 @@ jobs:
137169
setOutput("instructions", body);
138170
setOutput("crabbox_provider", provider);
139171
setOutput("lease_id", inputs.crabbox_lease_id || "");
172+
setOutput("publish_artifact_name", inputs.publish_artifact_name || "");
173+
setOutput("publish_run_id", inputs.publish_run_id || "");
140174
setOutput("request_source", eventName);
141175
142176
if (eventName === "issue_comment") {
@@ -151,6 +185,7 @@ jobs:
151185
validate_refs:
152186
name: Validate selected refs
153187
needs: resolve_request
188+
if: needs.resolve_request.outputs.publish_artifact_name == ''
154189
runs-on: ubuntu-24.04
155190
outputs:
156191
baseline_revision: ${{ steps.validate.outputs.baseline_revision }}
@@ -229,6 +264,7 @@ jobs:
229264
run_telegram_desktop_proof:
230265
name: Run agentic native Telegram proof
231266
needs: [resolve_request, validate_refs]
267+
if: needs.resolve_request.outputs.publish_artifact_name == ''
232268
runs-on: blacksmith-16vcpu-ubuntu-2404
233269
timeout-minutes: 360
234270
environment: qa-live-shared
@@ -473,3 +509,92 @@ jobs:
473509
run: |
474510
echo "Mantis Telegram desktop proof failed: comparison=${COMPARISON_STATUS:-unset}." >&2
475511
exit 1
512+
513+
publish_existing_telegram_desktop_proof:
514+
name: Publish existing native Telegram proof
515+
needs: resolve_request
516+
if: needs.resolve_request.outputs.publish_artifact_name != ''
517+
runs-on: ubuntu-24.04
518+
environment: qa-live-shared
519+
steps:
520+
- name: Checkout harness ref
521+
uses: actions/checkout@v6
522+
with:
523+
persist-credentials: false
524+
525+
- name: Setup Node environment
526+
uses: ./.github/actions/setup-node-env
527+
with:
528+
node-version: ${{ env.NODE_VERSION }}
529+
pnpm-version: ${{ env.PNPM_VERSION }}
530+
install-bun: "true"
531+
532+
- name: Download existing proof artifact
533+
env:
534+
GH_TOKEN: ${{ github.token }}
535+
PUBLISH_ARTIFACT_NAME: ${{ needs.resolve_request.outputs.publish_artifact_name }}
536+
PUBLISH_RUN_ID: ${{ needs.resolve_request.outputs.publish_run_id }}
537+
shell: bash
538+
run: |
539+
set -euo pipefail
540+
if [[ -z "${PUBLISH_RUN_ID:-}" ]]; then
541+
echo "publish_run_id is required when publish_artifact_name is set." >&2
542+
exit 1
543+
fi
544+
run_id="$PUBLISH_RUN_ID"
545+
gh run download "$run_id" \
546+
--repo "$GITHUB_REPOSITORY" \
547+
--name "$PUBLISH_ARTIFACT_NAME" \
548+
--dir "$MANTIS_OUTPUT_DIR"
549+
550+
artifacts_json="$(
551+
gh api \
552+
-H "Accept: application/vnd.github+json" \
553+
"repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts"
554+
)"
555+
artifact_id="$(jq -r --arg name "$PUBLISH_ARTIFACT_NAME" '.artifacts[] | select(.name == $name) | .id' <<<"$artifacts_json" | head -n 1)"
556+
if [[ -z "$artifact_id" || "$artifact_id" == "null" ]]; then
557+
echo "Could not resolve artifact id for '${PUBLISH_ARTIFACT_NAME}' in run ${run_id}." >&2
558+
exit 1
559+
fi
560+
echo "PUBLISH_RUN_ID=${run_id}" >> "$GITHUB_ENV"
561+
echo "PUBLISH_ARTIFACT_URL=https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts/${artifact_id}" >> "$GITHUB_ENV"
562+
563+
- name: Create Mantis GitHub App token
564+
id: mantis_app_token
565+
uses: actions/create-github-app-token@v3
566+
with:
567+
app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
568+
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
569+
owner: ${{ github.repository_owner }}
570+
repositories: ${{ github.event.repository.name }}
571+
permission-issues: write
572+
permission-pull-requests: write
573+
574+
- name: Comment PR with inline QA evidence
575+
env:
576+
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
577+
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
578+
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
579+
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
580+
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
581+
MANTIS_ARTIFACT_R2_REGION: auto
582+
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
583+
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
584+
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
585+
shell: bash
586+
run: |
587+
set -euo pipefail
588+
root="$MANTIS_OUTPUT_DIR"
589+
if [[ ! -f "$root/mantis-evidence.json" ]]; then
590+
echo "Downloaded artifact does not contain ${root}/mantis-evidence.json." >&2
591+
exit 1
592+
fi
593+
node scripts/mantis/publish-pr-evidence.mjs \
594+
--manifest "$root/mantis-evidence.json" \
595+
--target-pr "$TARGET_PR" \
596+
--artifact-root "mantis/telegram-desktop/pr-${TARGET_PR}/published-${PUBLISH_RUN_ID}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
597+
--marker "<!-- mantis-telegram-desktop-proof -->" \
598+
--artifact-url "$PUBLISH_ARTIFACT_URL" \
599+
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${PUBLISH_RUN_ID}" \
600+
--request-source "$REQUEST_SOURCE"

test/scripts/mantis-telegram-desktop-proof-workflow.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,32 @@ type WorkflowStep = {
1414
name?: string;
1515
run?: string;
1616
uses?: string;
17+
with?: Record<string, string>;
1718
};
1819

1920
type WorkflowJob = {
21+
if?: string;
2022
steps?: WorkflowStep[];
2123
};
2224

2325
type Workflow = {
2426
concurrency?: unknown;
2527
env?: Record<string, string>;
2628
jobs?: Record<string, WorkflowJob>;
29+
on?: {
30+
pull_request_target?: {
31+
types?: string[];
32+
};
33+
workflow_dispatch?: {
34+
inputs?: Record<
35+
string,
36+
{
37+
required?: boolean;
38+
type?: string;
39+
}
40+
>;
41+
};
42+
};
2743
permissions?: Record<string, string>;
2844
};
2945

@@ -102,6 +118,39 @@ describe("Mantis Telegram Desktop proof workflow", () => {
102118
expect(workflow).not.toContain('"/mantis"');
103119
});
104120

121+
it("runs when ClawSweeper applies the Telegram proof label", () => {
122+
const workflow = parse(readFileSync(WORKFLOW, "utf8")) as Workflow;
123+
const workflowText = readFileSync(WORKFLOW, "utf8");
124+
125+
expect(workflow.on?.pull_request_target?.types).toContain("labeled");
126+
expect(workflowText).toContain("github.event.label.name == 'mantis: telegram-visible-proof'");
127+
expect(workflowText).toContain('eventName === "pull_request_target"');
128+
expect(workflowText).toContain("context.payload.pull_request?.number");
129+
expect(workflowText).toContain("Accepted Mantis label trigger");
130+
});
131+
132+
it("can publish an existing proof artifact without recapturing", () => {
133+
const workflow = parse(readFileSync(WORKFLOW, "utf8")) as Workflow;
134+
const workflowText = readFileSync(WORKFLOW, "utf8");
135+
const publishJob = workflow.jobs?.publish_existing_telegram_desktop_proof;
136+
const captureJob = workflow.jobs?.run_telegram_desktop_proof;
137+
const validateJob = workflow.jobs?.validate_refs;
138+
139+
expect(workflow.on?.workflow_dispatch?.inputs?.publish_artifact_name?.required).toBe(false);
140+
expect(workflow.on?.workflow_dispatch?.inputs?.publish_run_id?.required).toBe(false);
141+
expect(captureJob?.if).toBe("needs.resolve_request.outputs.publish_artifact_name == ''");
142+
expect(validateJob?.if).toBe("needs.resolve_request.outputs.publish_artifact_name == ''");
143+
expect(publishJob?.if).toBe("needs.resolve_request.outputs.publish_artifact_name != ''");
144+
expect(workflowText).toContain("publish_run_id is required when publish_artifact_name is set.");
145+
expect(workflowText).toContain('gh run download "$run_id"');
146+
expect(workflowText).toContain(
147+
'--artifact-root "mantis/telegram-desktop/pr-${TARGET_PR}/published-',
148+
);
149+
expect(workflowText).toContain(
150+
"PUBLISH_ARTIFACT_URL=https://github.com/${GITHUB_REPOSITORY}/actions/runs/",
151+
);
152+
});
153+
105154
it("uses the repo-owned Telegram user driver by default", () => {
106155
expect(existsSync(USER_DRIVER)).toBe(true);
107156
expect(readFileSync(PROOF_SCRIPT, "utf8")).toContain(

0 commit comments

Comments
 (0)