

























@@ -0,0 +1,256 @@
1+name: Mantis Discord Status Reactions
2+3+on:
4+workflow_dispatch:
5+inputs:
6+baseline_ref:
7+description: Ref, tag, or SHA expected to reproduce queued-only behavior
8+required: true
9+default: 0bf06e953fdda290799fc9fb9244a8f67fdae593
10+type: string
11+candidate_ref:
12+description: Ref, tag, or SHA expected to show queued -> thinking -> done
13+required: true
14+default: main
15+type: string
16+17+permissions:
18+contents: read
19+pull-requests: read
20+21+concurrency:
22+group: mantis-discord-status-reactions-${{ inputs.baseline_ref }}-${{ inputs.candidate_ref }}-${{ github.run_attempt }}
23+cancel-in-progress: false
24+25+env:
26+FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
27+NODE_VERSION: "24.x"
28+PNPM_VERSION: "10.33.0"
29+OPENCLAW_BUILD_PRIVATE_QA: "1"
30+OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
31+32+jobs:
33+authorize_actor:
34+name: Authorize workflow actor
35+runs-on: blacksmith-8vcpu-ubuntu-2404
36+steps:
37+ - name: Require maintainer-level repository access
38+uses: actions/github-script@v8
39+with:
40+script: |
41+ const allowed = new Set(["admin", "maintain", "write"]);
42+ const { owner, repo } = context.repo;
43+ const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
44+ owner,
45+ repo,
46+ username: context.actor,
47+ });
48+ const permission = data.permission;
49+ core.info(`Actor ${context.actor} permission: ${permission}`);
50+ if (!allowed.has(permission)) {
51+ core.setFailed(
52+ `Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
53+ );
54+ }
55+56+ validate_refs:
57+name: Validate selected refs
58+needs: authorize_actor
59+runs-on: blacksmith-8vcpu-ubuntu-2404
60+outputs:
61+baseline_revision: ${{ steps.validate.outputs.baseline_revision }}
62+candidate_revision: ${{ steps.validate.outputs.candidate_revision }}
63+steps:
64+ - name: Checkout harness ref
65+uses: actions/checkout@v6
66+with:
67+persist-credentials: false
68+fetch-depth: 0
69+70+ - name: Validate refs are trusted
71+id: validate
72+env:
73+GH_TOKEN: ${{ github.token }}
74+BASELINE_REF: ${{ inputs.baseline_ref }}
75+CANDIDATE_REF: ${{ inputs.candidate_ref }}
76+shell: bash
77+run: |
78+ set -euo pipefail
79+80+ git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
81+82+ validate_ref() {
83+ local label="$1"
84+ local input_ref="$2"
85+ local revision=""
86+ local reason=""
87+88+ revision="$(git rev-parse "${input_ref}^{commit}")"
89+ if git merge-base --is-ancestor "$revision" refs/remotes/origin/main; then
90+ reason="main-ancestor"
91+ elif git tag --points-at "$revision" | grep -Eq '^v'; then
92+ reason="release-tag"
93+ else
94+ local pr_head_count
95+ pr_head_count="$(
96+ gh api \
97+ -H "Accept: application/vnd.github+json" \
98+ "repos/${GITHUB_REPOSITORY}/commits/${revision}/pulls" \
99+ --jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${revision}"'")] | length'
100+ )"
101+ if [[ "$pr_head_count" != "0" ]]; then
102+ reason="open-pr-head"
103+ fi
104+ fi
105+106+ if [[ -z "$reason" ]]; then
107+ echo "${label} ref '${input_ref}' resolved to ${revision}, which is not trusted for this secret-bearing Mantis run." >&2
108+ exit 1
109+ fi
110+111+ echo "${label}_revision=${revision}" >> "$GITHUB_OUTPUT"
112+ {
113+ echo "${label}: \`${input_ref}\`"
114+ echo "${label} SHA: \`${revision}\`"
115+ echo "${label} trust reason: \`${reason}\`"
116+ } >> "$GITHUB_STEP_SUMMARY"
117+ }
118+119+ validate_ref baseline "$BASELINE_REF"
120+ validate_ref candidate "$CANDIDATE_REF"
121+122+ run_status_reactions:
123+name: Run Discord status reaction before/after
124+needs: validate_refs
125+runs-on: blacksmith-8vcpu-ubuntu-2404
126+timeout-minutes: 180
127+environment: qa-live-shared
128+steps:
129+ - name: Checkout harness ref
130+uses: actions/checkout@v6
131+with:
132+persist-credentials: false
133+fetch-depth: 0
134+135+ - name: Setup Node environment
136+uses: ./.github/actions/setup-node-env
137+with:
138+node-version: ${{ env.NODE_VERSION }}
139+pnpm-version: ${{ env.PNPM_VERSION }}
140+install-bun: "true"
141+142+ - name: Build Mantis harness
143+run: pnpm build
144+145+ - name: Prepare baseline and candidate worktrees
146+shell: bash
147+env:
148+BASELINE_SHA: ${{ needs.validate_refs.outputs.baseline_revision }}
149+CANDIDATE_SHA: ${{ needs.validate_refs.outputs.candidate_revision }}
150+run: |
151+ set -euo pipefail
152+153+ mkdir -p .artifacts/qa-e2e/mantis/discord-status-reactions/worktrees
154+ git worktree add --detach .artifacts/qa-e2e/mantis/discord-status-reactions/worktrees/baseline "$BASELINE_SHA"
155+ git worktree add --detach .artifacts/qa-e2e/mantis/discord-status-reactions/worktrees/candidate "$CANDIDATE_SHA"
156+157+ for lane in baseline candidate; do
158+ lane_dir=".artifacts/qa-e2e/mantis/discord-status-reactions/worktrees/${lane}"
159+ echo "Installing ${lane} worktree dependencies"
160+ pnpm --dir "$lane_dir" install --frozen-lockfile
161+ echo "Building ${lane} worktree"
162+ pnpm --dir "$lane_dir" build
163+ done
164+165+ - name: Run baseline and candidate
166+id: run_mantis
167+shell: bash
168+env:
169+OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
170+OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
171+OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
172+OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
173+OPENCLAW_QA_DISCORD_CAPTURE_CONTENT: "1"
174+run: |
175+ set -euo pipefail
176+177+ require_var() {
178+ local key="$1"
179+ if [[ -z "${!key:-}" ]]; then
180+ echo "Missing required ${key}." >&2
181+ exit 1
182+ fi
183+ }
184+185+ require_var OPENAI_API_KEY
186+ require_var OPENCLAW_QA_CONVEX_SITE_URL
187+ require_var OPENCLAW_QA_CONVEX_SECRET_CI
188+189+ root=".artifacts/qa-e2e/mantis/discord-status-reactions"
190+ mkdir -p "$root"
191+ echo "output_dir=${root}" >> "$GITHUB_OUTPUT"
192+193+ run_lane() {
194+ local lane="$1"
195+ local repo_root="$root/worktrees/$lane"
196+ local output_dir="$root/$lane"
197+ pnpm openclaw qa discord \
198+ --repo-root "$repo_root" \
199+ --output-dir "$output_dir" \
200+ --provider-mode live-frontier \
201+ --model openai/gpt-5.4 \
202+ --alt-model openai/gpt-5.4 \
203+ --fast \
204+ --credential-source convex \
205+ --credential-role ci \
206+ --scenario discord-status-reactions-tool-only \
207+ --allow-failures
208+ }
209+210+ run_lane baseline
211+ run_lane candidate
212+213+ baseline_status="$(jq -r '.scenarios[0].status' "$root/baseline/discord-qa-summary.json")"
214+ candidate_status="$(jq -r '.scenarios[0].status' "$root/candidate/discord-qa-summary.json")"
215+216+ jq -n \
217+ --arg baseline_status "$baseline_status" \
218+ --arg candidate_status "$candidate_status" \
219+ --arg baseline_sha "${{ needs.validate_refs.outputs.baseline_revision }}" \
220+ --arg candidate_sha "${{ needs.validate_refs.outputs.candidate_revision }}" \
221+ '{
222+ scenario: "discord-status-reactions-tool-only",
223+ baseline: { sha: $baseline_sha, expected: "queued-only", status: $baseline_status, reproduced: ($baseline_status == "fail") },
224+ candidate: { sha: $candidate_sha, expected: "queued -> thinking -> done", status: $candidate_status, fixed: ($candidate_status == "pass") },
225+ pass: (($baseline_status == "fail") and ($candidate_status == "pass"))
226+ }' > "$root/comparison.json"
227+228+ {
229+ echo "# Mantis Discord Status Reactions"
230+ echo
231+ echo "- Scenario: \`discord-status-reactions-tool-only\`"
232+ echo "- Baseline status: \`${baseline_status}\`"
233+ echo "- Candidate status: \`${candidate_status}\`"
234+ echo "- Baseline screenshot: \`baseline/discord-status-reactions-tool-only-timeline.png\`"
235+ echo "- Candidate screenshot: \`candidate/discord-status-reactions-tool-only-timeline.png\`"
236+ } > "$root/mantis-report.md"
237+238+ cat "$root/mantis-report.md" >> "$GITHUB_STEP_SUMMARY"
239+240+ if [[ "$baseline_status" != "fail" ]]; then
241+ echo "Baseline did not reproduce queued-only behavior." >&2
242+ exit 1
243+ fi
244+ if [[ "$candidate_status" != "pass" ]]; then
245+ echo "Candidate did not show queued -> thinking -> done." >&2
246+ exit 1
247+ fi
248+249+ - name: Upload Mantis status reaction artifacts
250+if: always()
251+uses: actions/upload-artifact@v4
252+with:
253+name: mantis-discord-status-reactions-${{ github.run_id }}-${{ github.run_attempt }}
254+path: ${{ steps.run_mantis.outputs.output_dir }}
255+retention-days: 14
256+if-no-files-found: warn
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。