Files
openclaw/.github/workflows/mantis-resolve-request.yml
T
Peter Steinberger ff10db092b ci(mantis): extract shared request-resolution, ref-trust, and reaction workflows (#121339)
* ci(mantis): extract shared request, trust, and reaction workflows

* ci(mantis): drop caller-less params from shared resolve workflow

* test(mantis): read candidate-override parsing from shared resolve workflow
2026-08-09 22:04:27 -07:00

245 lines
9.7 KiB
YAML

name: Mantis Resolve Request
on:
workflow_call:
inputs:
request-pattern:
description: JavaScript regex source matched against the normalized comment body
required: true
type: string
exclude-pattern:
description: Optional JavaScript regex source that suppresses a matching request
required: false
default: ""
type: string
skip-notice:
description: Notice emitted when a Mantis mention does not request this workflow
required: true
type: string
dispatch-params:
description: Workflow-specific dispatch inputs or issue-comment defaults as JSON
required: false
default: "{}"
type: string
runner:
description: Runner used for request resolution
required: false
default: ubuntu-24.04
type: string
secrets:
MANTIS_GITHUB_APP_ID:
required: true
MANTIS_GITHUB_APP_PRIVATE_KEY:
required: true
outputs:
should_run:
value: ${{ jobs.resolve.outputs.should_run }}
request_source:
value: ${{ jobs.resolve.outputs.request_source }}
pr_number:
value: ${{ jobs.resolve.outputs.pr_number }}
candidate_ref:
value: ${{ jobs.resolve.outputs.candidate_ref }}
baseline_ref:
value: ${{ jobs.resolve.outputs.baseline_ref }}
reaction_id:
value: ${{ jobs.resolve.outputs.reaction_id }}
params:
value: ${{ jobs.resolve.outputs.params }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
resolve:
name: Resolve Mantis request
runs-on: ${{ inputs.runner }}
outputs:
baseline_ref: ${{ steps.resolve.outputs.baseline_ref }}
candidate_ref: ${{ steps.resolve.outputs.candidate_ref }}
params: ${{ steps.resolve.outputs.params }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
reaction_id: ${{ steps.add_reaction.outputs.reaction_id }}
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
env:
DISPATCH_PARAMS: ${{ inputs.dispatch-params }}
EXCLUDE_PATTERN: ${{ inputs.exclude-pattern }}
REQUEST_PATTERN: ${{ inputs.request-pattern }}
SKIP_NOTICE: ${{ inputs.skip-notice }}
with:
script: |
const eventName = context.eventName;
const dispatchParams = JSON.parse(process.env.DISPATCH_PARAMS || "{}");
function stringParam(value) {
return typeof value === "string" ? value : "";
}
function setOutput(name, value) {
core.setOutput(name, value ?? "");
core.info(`${name}=${value ?? ""}`);
}
function setResolvedOutputs({ baselineRef, candidateRef, params, prNumber, source }) {
setOutput("should_run", "true");
setOutput("baseline_ref", baselineRef);
setOutput("candidate_ref", candidateRef);
setOutput("pr_number", prNumber);
setOutput("request_source", source);
setOutput("params", JSON.stringify(params));
}
async function getPullRequest(pullNumber) {
const { owner, repo } = context.repo;
const { data: pullRequest } = await github.rest.pulls.get({
owner,
repo,
pull_number: Number(pullNumber),
});
return pullRequest;
}
if (eventName === "workflow_dispatch") {
const prNumber = stringParam(dispatchParams.pr_number);
const pullRequest = prNumber ? await getPullRequest(prNumber) : undefined;
const candidateRef = stringParam(dispatchParams.candidate_ref) || pullRequest?.head.sha || "";
const baselineRef = stringParam(dispatchParams.baseline_ref) || pullRequest?.base.sha || "";
const params = {
...dispatchParams,
baseline_ref: baselineRef,
candidate_ref: candidateRef,
crabbox_provider:
stringParam(dispatchParams.crabbox_provider) ||
stringParam(dispatchParams.provider),
lease_id:
stringParam(dispatchParams.crabbox_lease_id) ||
stringParam(dispatchParams.lease_id) ||
stringParam(dispatchParams.lease),
pr_number: prNumber,
provider_mode:
stringParam(dispatchParams.provider_mode) ||
stringParam(dispatchParams["provider-mode"]),
scenario: stringParam(dispatchParams.scenario),
};
setResolvedOutputs({
baselineRef,
candidateRef,
params,
prNumber,
source: "workflow_dispatch",
});
return;
}
if (eventName !== "issue_comment") {
core.setFailed(`Unsupported event: ${eventName}`);
return;
}
const issue = context.payload.issue;
const body = context.payload.comment?.body ?? "";
if (!issue?.pull_request) {
core.setFailed("Mantis issue_comment trigger requires a pull request comment.");
return;
}
const normalized = body.toLowerCase();
const mentionsMantis =
normalized.includes("@openclaw-mantis") ||
normalized.includes("/openclaw-mantis");
const requested = new RegExp(process.env.REQUEST_PATTERN, "u").test(normalized);
const excluded =
process.env.EXCLUDE_PATTERN !== "" &&
new RegExp(process.env.EXCLUDE_PATTERN, "u").test(normalized);
if (!mentionsMantis || !requested || excluded) {
core.notice(process.env.SKIP_NOTICE);
setOutput("should_run", "false");
setOutput("baseline_ref", "");
setOutput("candidate_ref", "");
setOutput("pr_number", "");
setOutput("request_source", "unsupported_issue_comment");
setOutput("params", "{}");
return;
}
const pullRequest = await getPullRequest(issue.number);
const token = (keys) => {
const pattern = new RegExp(
`\\b(?:${keys.join("|")})\\s*[:=]\\s*\`?([^\\s\`]+)\`?`,
"i",
);
return body.match(pattern)?.[1] ?? "";
};
const rawCandidate = token(["candidate", "head"]);
const candidateRef =
rawCandidate && !["head", "pr", "pr-head"].includes(rawCandidate.toLowerCase())
? rawCandidate
: pullRequest.head.sha;
const baselineRef =
token(["baseline", "base"]) ||
stringParam(dispatchParams.baseline_ref) ||
pullRequest.base.sha;
const params = {
...dispatchParams,
baseline_ref: baselineRef,
candidate_ref: candidateRef,
crabbox_provider:
token(["provider", "crabbox_provider"]) ||
stringParam(dispatchParams.crabbox_provider) ||
stringParam(dispatchParams.provider),
lease_id:
token(["lease", "lease_id", "crabbox_lease_id"]) ||
stringParam(dispatchParams.crabbox_lease_id) ||
stringParam(dispatchParams.lease_id) ||
stringParam(dispatchParams.lease),
pr_number: String(issue.number),
provider_mode:
token(["provider_mode", "provider-mode"]) ||
stringParam(dispatchParams.provider_mode) ||
stringParam(dispatchParams["provider-mode"]),
scenario:
token(["scenario", "scenarios"]) || stringParam(dispatchParams.scenario),
};
setResolvedOutputs({
baselineRef,
candidateRef,
params,
prNumber: String(issue.number),
source: "issue_comment",
});
- name: Create Mantis reaction GitHub App token
id: mantis_reaction_token
if: ${{ steps.resolve.outputs.request_source == 'issue_comment' }}
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
- name: Add Mantis eyes reaction
id: add_reaction
if: ${{ steps.resolve.outputs.request_source == 'issue_comment' && steps.mantis_reaction_token.outcome == 'success' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ steps.mantis_reaction_token.outputs.token }}
script: |
const { owner, repo } = context.repo;
await github.rest.reactions
.createForIssueComment({
owner,
repo,
comment_id: context.payload.comment.id,
content: "eyes",
})
.then(({ data: reaction }) => core.setOutput("reaction_id", String(reaction.id)))
.catch((error) => core.warning(`Could not add eyes reaction: ${error.message}`));