mirror of
https://github.com/openclaw/lobster.git
synced 2026-08-14 00:48:09 +00:00
ci: add ClawSweeper dispatch workflow
This commit is contained in:
@@ -0,0 +1,202 @@
|
|||||||
|
name: ClawSweeper Dispatch
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, reopened, edited, labeled, unlabeled]
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned external dispatch; no checkout or untrusted PR code execution
|
||||||
|
types: [opened, reopened, synchronize, ready_for_review, edited, labeled, unlabeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: clawsweeper-dispatch-${{ github.repository }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}
|
||||||
|
cancel-in-progress: ${{ github.event.action == 'edited' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dispatch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
|
||||||
|
env:
|
||||||
|
HAS_CLAWSWEEPER_APP_PRIVATE_KEY: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY != '' }}
|
||||||
|
CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093
|
||||||
|
SUPERSEDES_IN_PROGRESS: ${{ (github.event.action == 'edited' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review') && 'true' || 'false' }}
|
||||||
|
steps:
|
||||||
|
- name: Debounce bursty metadata events
|
||||||
|
if: ${{ github.event.action == 'labeled' || github.event.action == 'unlabeled' }}
|
||||||
|
run: sleep 20
|
||||||
|
|
||||||
|
- name: Create ClawSweeper dispatch token
|
||||||
|
id: token
|
||||||
|
if: ${{ env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }}
|
||||||
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||||
|
with:
|
||||||
|
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
|
||||||
|
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
|
||||||
|
owner: openclaw
|
||||||
|
repositories: clawsweeper
|
||||||
|
permission-contents: write
|
||||||
|
|
||||||
|
- name: Pre-filter ClawSweeper comment
|
||||||
|
id: comment_filter
|
||||||
|
if: ${{ github.event_name == 'issue_comment' }}
|
||||||
|
env:
|
||||||
|
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if grep -Eiq '(^|[[:space:]])@(clawsweeper|openclaw-clawsweeper)\b(\[bot\])?|(^|[[:space:]])/(clawsweeper|review|autoclose|auto([[:space:]]+|-)?merge)\b' <<< "$COMMENT_BODY"; then
|
||||||
|
echo "is_command=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "is_command=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create target comment token
|
||||||
|
id: target_token
|
||||||
|
if: >-
|
||||||
|
${{
|
||||||
|
github.event_name == 'issue_comment' &&
|
||||||
|
steps.comment_filter.outputs.is_command == 'true' &&
|
||||||
|
env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true'
|
||||||
|
}}
|
||||||
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||||
|
with:
|
||||||
|
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
|
||||||
|
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
repositories: ${{ github.event.repository.name }}
|
||||||
|
permission-issues: write
|
||||||
|
permission-pull-requests: read
|
||||||
|
|
||||||
|
- name: Dispatch exact ClawSweeper review
|
||||||
|
if: ${{ github.event_name != 'issue_comment' }}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
|
TARGET_REPO: ${{ github.repository }}
|
||||||
|
ITEM_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
||||||
|
ITEM_KIND: ${{ github.event_name == 'pull_request_target' && 'pull_request' || 'issue' }}
|
||||||
|
SOURCE_EVENT: ${{ github.event_name }}
|
||||||
|
SOURCE_ACTION: ${{ github.event.action }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$GH_TOKEN" ]; then
|
||||||
|
echo "::notice::Skipping ClawSweeper dispatch because no dispatch credential is configured."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
ingress_fingerprint="$(node <<'NODE'
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
|
||||||
|
const pullRequest = event.pull_request && typeof event.pull_request === "object"
|
||||||
|
? event.pull_request
|
||||||
|
: {};
|
||||||
|
const headSha = String(pullRequest.head?.sha || "").trim().toLowerCase();
|
||||||
|
const updatedAt = String(pullRequest.updated_at || "").trim();
|
||||||
|
if (
|
||||||
|
process.env.ITEM_KIND !== "pull_request" ||
|
||||||
|
!/^[0-9a-f]{40}$/.test(headSha) ||
|
||||||
|
!updatedAt
|
||||||
|
) {
|
||||||
|
process.stdout.write("");
|
||||||
|
} else {
|
||||||
|
process.stdout.write(
|
||||||
|
crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(
|
||||||
|
JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
target_repo: String(process.env.TARGET_REPO || "").toLowerCase(),
|
||||||
|
item_number: Number(process.env.ITEM_NUMBER),
|
||||||
|
action: String(process.env.SOURCE_ACTION || ""),
|
||||||
|
head_sha: headSha,
|
||||||
|
updated_at: updatedAt,
|
||||||
|
body: typeof pullRequest.body === "string" ? pullRequest.body : "",
|
||||||
|
label: String(event.label?.name || ""),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.digest("hex"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
)"
|
||||||
|
payload="$(jq -nc \
|
||||||
|
--arg target_repo "$TARGET_REPO" \
|
||||||
|
--argjson item_number "$ITEM_NUMBER" \
|
||||||
|
--arg item_kind "$ITEM_KIND" \
|
||||||
|
--arg source_event "$SOURCE_EVENT" \
|
||||||
|
--arg source_action "$SOURCE_ACTION" \
|
||||||
|
--arg ingress_fingerprint "$ingress_fingerprint" \
|
||||||
|
--argjson supersedes_in_progress "$SUPERSEDES_IN_PROGRESS" \
|
||||||
|
'{event_type:"clawsweeper_item",client_payload:({target_repo:$target_repo,item_number:$item_number,item_kind:$item_kind,source_event:$source_event,source_action:$source_action,supersedes_in_progress:$supersedes_in_progress} + (if $ingress_fingerprint != "" then {ingress_route:"target_dispatcher",ingress_fingerprint:$ingress_fingerprint} else {} end))}')"
|
||||||
|
gh api repos/openclaw/clawsweeper/dispatches \
|
||||||
|
--method POST \
|
||||||
|
--input - <<< "$payload"
|
||||||
|
|
||||||
|
- name: Acknowledge and dispatch ClawSweeper comment
|
||||||
|
if: >-
|
||||||
|
${{
|
||||||
|
github.event_name == 'issue_comment' &&
|
||||||
|
steps.comment_filter.outputs.is_command == 'true'
|
||||||
|
}}
|
||||||
|
env:
|
||||||
|
DISPATCH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
|
TARGET_TOKEN: ${{ steps.target_token.outputs.token }}
|
||||||
|
TARGET_REPO: ${{ github.repository }}
|
||||||
|
ITEM_NUMBER: ${{ github.event.issue.number }}
|
||||||
|
COMMENT_ID: ${{ github.event.comment.id }}
|
||||||
|
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||||
|
AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }}
|
||||||
|
SOURCE_ACTION: ${{ github.event.action }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$DISPATCH_TOKEN" ]; then
|
||||||
|
echo "::notice::Skipping ClawSweeper dispatch because no dispatch credential is configured."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
body_file="$RUNNER_TEMP/clawsweeper-comment-body.txt"
|
||||||
|
printf '%s\n' "$COMMENT_BODY" > "$body_file"
|
||||||
|
if grep -Eiq '<!--[[:space:]]*clawsweeper-proof-nudge([[:space:]]|-->)' "$body_file"; then
|
||||||
|
echo "Ignoring ClawSweeper proof-nudge comment."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ -n "$TARGET_TOKEN" ]; then
|
||||||
|
GH_TOKEN="$TARGET_TOKEN" gh api -X POST \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"repos/$TARGET_REPO/issues/comments/$COMMENT_ID/reactions" \
|
||||||
|
-f content="eyes" >/dev/null || true
|
||||||
|
fi
|
||||||
|
status_comment_id=""
|
||||||
|
if [ -n "$TARGET_TOKEN" ]; then
|
||||||
|
case "$AUTHOR_ASSOCIATION" in
|
||||||
|
OWNER|MEMBER|COLLABORATOR)
|
||||||
|
status_body="$(printf '%s\n' \
|
||||||
|
"<!-- clawsweeper-command-ack:$COMMENT_ID -->" \
|
||||||
|
"🦞👀" \
|
||||||
|
"ClawSweeper picked this up." \
|
||||||
|
"" \
|
||||||
|
"Command router queued. I will update this comment with the next step.")"
|
||||||
|
status_payload="$(jq -nc --arg body "$status_body" '{body:$body}')"
|
||||||
|
status_err="$(mktemp)"
|
||||||
|
if status_response="$(GH_TOKEN="$TARGET_TOKEN" gh api \
|
||||||
|
"repos/$TARGET_REPO/issues/$ITEM_NUMBER/comments" \
|
||||||
|
--method POST \
|
||||||
|
--input - <<< "$status_payload" 2>"$status_err")"; then
|
||||||
|
status_comment_id="$(jq -r '.id // empty' <<< "$status_response")"
|
||||||
|
else
|
||||||
|
cat "$status_err" >&2
|
||||||
|
echo "::warning::Could not create ClawSweeper queued status comment; dispatching command router without one."
|
||||||
|
fi
|
||||||
|
rm -f "$status_err"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
payload="$(jq -nc \
|
||||||
|
--arg target_repo "$TARGET_REPO" \
|
||||||
|
--argjson item_number "$ITEM_NUMBER" \
|
||||||
|
--argjson comment_id "$COMMENT_ID" \
|
||||||
|
--arg status_comment_id "$status_comment_id" \
|
||||||
|
--arg source_event "issue_comment" \
|
||||||
|
--arg source_action "$SOURCE_ACTION" \
|
||||||
|
'{event_type:"clawsweeper_comment",client_payload:({target_repo:$target_repo,item_number:$item_number,comment_id:$comment_id,source_event:$source_event,source_action:$source_action,max_comments:"1"} + (if $status_comment_id != "" then {status_comment_id:($status_comment_id|tonumber)} else {} end))}')"
|
||||||
|
GH_TOKEN="$DISPATCH_TOKEN" gh api repos/openclaw/clawsweeper/dispatches \
|
||||||
|
--method POST \
|
||||||
|
--input - <<< "$payload"
|
||||||
Reference in New Issue
Block a user