mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-11 00:25:36 +08:00
537 lines
24 KiB
YAML
537 lines
24 KiB
YAML
name: PR-Agent
|
||
|
||
on:
|
||
pull_request_target:
|
||
# PR-Agent 通过 base repo 上下文读取 PR diff 并发布 Review,不 checkout 或执行 PR 分支代码。
|
||
# pull_request_target 允许 fork PR 使用仓库 secrets,因此 workflow 只运行固定 digest 的 PR-Agent 容器。
|
||
types:
|
||
- opened
|
||
- reopened
|
||
- ready_for_review
|
||
- review_requested
|
||
- synchronize
|
||
issue_comment:
|
||
# 手动命令只在 PR 评论中有意义;编辑后的命令也可重新触发。
|
||
types:
|
||
- created
|
||
- edited
|
||
|
||
permissions:
|
||
# 读取仓库内容和 PR diff。
|
||
contents: read
|
||
# 更新 PR 描述、发布 PR Review 或修改 PR 相关元数据。
|
||
pull-requests: write
|
||
# PR 评论在 GitHub API 中属于 issue comments,手动问答需要该权限。
|
||
issues: write
|
||
|
||
jobs:
|
||
pr-agent:
|
||
if: >-
|
||
github.event.sender.type != 'Bot' &&
|
||
(
|
||
(
|
||
github.event_name == 'pull_request_target'
|
||
) ||
|
||
(
|
||
github.event_name == 'issue_comment' &&
|
||
github.event.issue.pull_request != null &&
|
||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIME_CONTRIBUTOR"]'), github.event.comment.author_association) &&
|
||
(
|
||
github.event.comment.body == '/describe' ||
|
||
startsWith(github.event.comment.body, '/describe ') ||
|
||
github.event.comment.body == '/review' ||
|
||
startsWith(github.event.comment.body, '/review ') ||
|
||
github.event.comment.body == '/ask' ||
|
||
startsWith(github.event.comment.body, '/ask ')
|
||
)
|
||
)
|
||
)
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number }}
|
||
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }}
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 20
|
||
|
||
steps:
|
||
- name: Capture PR context
|
||
id: pr_context
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||
run: |
|
||
set -euo pipefail
|
||
pull="$(mktemp)"
|
||
gh api "repos/${REPO}/pulls/${PR_NUMBER}" > "${pull}"
|
||
python3 - "${pull}" >> "${GITHUB_OUTPUT}" <<'PY'
|
||
import json
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
pull = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||
title = pull.get("title") or ""
|
||
body = pull.get("body") or ""
|
||
labels = {item.get("name", "") for item in pull.get("labels") or []}
|
||
skip_pr_agent = "true" if "skip pr-agent" in labels or re.search(r"^(?:\[Auto\]|Auto)", title) else "false"
|
||
body = re.sub(
|
||
r"<!-- pr-agent-summary:start -->.*?<!-- pr-agent-summary:end -->",
|
||
" ",
|
||
body,
|
||
flags=re.DOTALL,
|
||
)
|
||
text = f"{title}\n{body}"
|
||
cjk_count = len(re.findall(r"[\u4e00-\u9fff]", text))
|
||
latin_words = len(re.findall(r"\b[A-Za-z][A-Za-z]{2,}\b", text))
|
||
if cjk_count >= 4 or latin_words < 8:
|
||
response_language = "zh-CN"
|
||
summary_heading = "PR-Agent 摘要"
|
||
else:
|
||
response_language = "en-US"
|
||
summary_heading = "PR-Agent Summary"
|
||
print(f"head_sha={pull['head']['sha']}")
|
||
print(f"changed_files={pull.get('changed_files') or 0}")
|
||
print(f"response_language={response_language}")
|
||
print(f"summary_heading={summary_heading}")
|
||
print(f"skip_pr_agent={skip_pr_agent}")
|
||
PY
|
||
|
||
- name: Prepare PR-Agent description markers
|
||
if: >-
|
||
steps.pr_context.outputs.skip_pr_agent != 'true' &&
|
||
(
|
||
github.event_name == 'pull_request_target' ||
|
||
github.event.comment.body == '/describe' ||
|
||
startsWith(github.event.comment.body, '/describe ')
|
||
)
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||
SUMMARY_HEADING: ${{ steps.pr_context.outputs.summary_heading }}
|
||
CHANGED_FILES: ${{ steps.pr_context.outputs.changed_files }}
|
||
run: |
|
||
set -euo pipefail
|
||
payload="$(mktemp)"
|
||
python3 - "${payload}" <<'PY'
|
||
import json
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
|
||
pull = json.loads(subprocess.check_output(
|
||
["gh", "api", f"repos/{os.environ['REPO']}/pulls/{os.environ['PR_NUMBER']}"],
|
||
text=True,
|
||
))
|
||
body = pull.get("body") or ""
|
||
start = "<!-- pr-agent-summary:start -->"
|
||
end = "<!-- pr-agent-summary:end -->"
|
||
heading = os.environ.get("SUMMARY_HEADING") or "PR-Agent 摘要"
|
||
changed_files = int(os.environ.get("CHANGED_FILES") or 0)
|
||
block = f"## {heading}\n\n{start}\npr_agent:summary\n{end}"
|
||
start_index = body.find(start)
|
||
end_index = body.find(end, start_index + len(start)) if start_index >= 0 else -1
|
||
owned_block = re.compile(
|
||
r"(?ims)^##\s+(?:PR-Agent\s+摘要|PR-Agent\s+Summary)\s*\n\s*"
|
||
r"<!-- pr-agent-summary:start -->.*?<!-- pr-agent-summary:end -->\s*"
|
||
)
|
||
if changed_files == 0:
|
||
updated = owned_block.sub("", body)
|
||
if updated != body:
|
||
updated = updated.rstrip()
|
||
elif start_index >= 0 and end_index >= 0:
|
||
body = re.sub(
|
||
r"(?im)^##\s+(PR-Agent\s+摘要|PR-Agent\s+Summary)\s*\n\s*(?=<!-- pr-agent-summary:start -->)",
|
||
f"## {heading}\n\n",
|
||
body,
|
||
)
|
||
start_index = body.find(start)
|
||
end_index = body.find(end, start_index + len(start))
|
||
updated = body[:start_index] + f"{start}\npr_agent:summary\n{end}" + body[end_index + len(end):]
|
||
elif start_index >= 0 or end in body:
|
||
updated = body
|
||
elif body.strip():
|
||
updated = f"{body.rstrip()}\n\n{block}\n"
|
||
else:
|
||
updated = f"{block}\n"
|
||
if updated != body:
|
||
with open(sys.argv[1], "w", encoding="utf-8") as handle:
|
||
json.dump({"body": updated}, handle, ensure_ascii=False)
|
||
PY
|
||
if [ -s "${payload}" ]; then
|
||
gh api --method PATCH "repos/${REPO}/pulls/${PR_NUMBER}" --input "${payload}" >/dev/null
|
||
fi
|
||
|
||
- name: Update PR description
|
||
if: >-
|
||
steps.pr_context.outputs.skip_pr_agent != 'true' &&
|
||
steps.pr_context.outputs.changed_files != '0' &&
|
||
(
|
||
github.event_name == 'pull_request_target' ||
|
||
github.event.comment.body == '/describe' ||
|
||
startsWith(github.event.comment.body, '/describe ')
|
||
)
|
||
uses: docker://pragent/pr-agent:0.39.0-github_action@sha256:b253845caa8c7ff5ce8be78f32996647982bdd4890826a962b78eff2e385a825
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||
OPENAI.API_BASE: ${{ secrets.OPENAI_API_BASE }}
|
||
config.model: 'gpt-5.6-terra'
|
||
config.fallback_models: '["gpt-5.5", "gpt-5.4"]'
|
||
config.custom_model_max_tokens: '1050000'
|
||
config.reasoning_effort: 'medium'
|
||
config.ai_timeout: '900'
|
||
config.response_language: ${{ steps.pr_context.outputs.response_language }}
|
||
config.large_patch_policy: 'clip'
|
||
config.ignore_pr_title: '["^\\[Auto\\]", "^Auto"]'
|
||
config.ignore_pr_labels: '["skip pr-agent"]'
|
||
github_action_config.auto_review: 'false'
|
||
github_action_config.auto_describe: 'true'
|
||
github_action_config.auto_improve: 'false'
|
||
github_action_config.pr_actions: '["opened", "reopened", "ready_for_review", "review_requested"]'
|
||
github_action_config.handle_push_trigger: 'true'
|
||
github_action_config.push_commands: '["/describe"]'
|
||
pr_description.generate_ai_title: 'false'
|
||
pr_description.publish_labels: 'false'
|
||
pr_description.publish_description_as_comment: 'false'
|
||
pr_description.publish_description_as_comment_persistent: 'false'
|
||
pr_description.enable_pr_diagram: 'false'
|
||
pr_description.enable_pr_type: 'false'
|
||
pr_description.enable_help_text: 'false'
|
||
pr_description.enable_help_comment: 'false'
|
||
pr_description.enable_semantic_files_types: 'false'
|
||
pr_description.collapsible_file_list: 'adaptive'
|
||
pr_description.add_original_user_description: 'true'
|
||
pr_description.use_description_markers: 'true'
|
||
pr_description.final_update_message: 'false'
|
||
pr_description.extra_instructions: |
|
||
Match the configured response language.
|
||
Summarize the change goal, key implementation details, compatibility impact, tests, and notable risks.
|
||
Use 2-4 bullets for small pull requests and 4-8 bullets for larger changes.
|
||
Avoid file lists and local command transcripts.
|
||
|
||
- name: Remove unfilled PR-Agent description marker
|
||
if: >-
|
||
always() &&
|
||
steps.pr_context.outputs.skip_pr_agent != 'true' &&
|
||
(
|
||
github.event_name == 'pull_request_target' ||
|
||
github.event.comment.body == '/describe' ||
|
||
startsWith(github.event.comment.body, '/describe ')
|
||
)
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||
run: |
|
||
set -euo pipefail
|
||
payload="$(mktemp)"
|
||
python3 - "${payload}" <<'PY'
|
||
import json
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
|
||
pull = json.loads(subprocess.check_output(
|
||
["gh", "api", f"repos/{os.environ['REPO']}/pulls/{os.environ['PR_NUMBER']}"],
|
||
text=True,
|
||
))
|
||
body = pull.get("body") or ""
|
||
placeholder = "<!-- pr-agent-summary:start -->\npr_agent:summary\n<!-- pr-agent-summary:end -->"
|
||
owned_block = re.compile(
|
||
r"(?ims)^##\s+(?:PR-Agent\s+摘要|PR-Agent\s+Summary)\s*\n\s*"
|
||
r"<!-- pr-agent-summary:start -->\s*pr_agent:summary\s*<!-- pr-agent-summary:end -->\s*"
|
||
)
|
||
if placeholder in body:
|
||
updated = owned_block.sub("", body).rstrip()
|
||
if updated != body:
|
||
with open(sys.argv[1], "w", encoding="utf-8") as handle:
|
||
json.dump({"body": updated}, handle, ensure_ascii=False)
|
||
PY
|
||
if [ -s "${payload}" ]; then
|
||
gh api --method PATCH "repos/${REPO}/pulls/${PR_NUMBER}" --input "${payload}" >/dev/null
|
||
fi
|
||
|
||
- name: Analyze PR review
|
||
id: review_analysis
|
||
if: >-
|
||
steps.pr_context.outputs.skip_pr_agent != 'true' &&
|
||
(
|
||
github.event_name == 'pull_request_target' ||
|
||
github.event.comment.body == '/review' ||
|
||
startsWith(github.event.comment.body, '/review ')
|
||
)
|
||
uses: docker://pragent/pr-agent:0.39.0-github_action@sha256:b253845caa8c7ff5ce8be78f32996647982bdd4890826a962b78eff2e385a825
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||
OPENAI.API_BASE: ${{ secrets.OPENAI_API_BASE }}
|
||
config.model: ${{ github.event_name == 'issue_comment' && 'gpt-5.6-sol' || 'gpt-5.6-terra' }}
|
||
config.fallback_models: '["gpt-5.5", "gpt-5.4"]'
|
||
config.custom_model_max_tokens: '1050000'
|
||
config.reasoning_effort: 'xhigh'
|
||
config.ai_timeout: '900'
|
||
config.response_language: ${{ steps.pr_context.outputs.response_language }}
|
||
config.large_patch_policy: 'clip'
|
||
config.ignore_pr_title: '["^\\[Auto\\]", "^Auto"]'
|
||
config.ignore_pr_labels: '["skip pr-agent"]'
|
||
config.publish_output: 'false'
|
||
github_action_config.auto_review: 'true'
|
||
github_action_config.auto_describe: 'false'
|
||
github_action_config.auto_improve: 'false'
|
||
github_action_config.pr_actions: '["opened", "reopened", "ready_for_review", "review_requested"]'
|
||
github_action_config.handle_push_trigger: 'true'
|
||
github_action_config.push_commands: '["/review"]'
|
||
github_action_config.enable_output: 'true'
|
||
pr_reviewer.num_max_findings: '4'
|
||
pr_reviewer.require_score_review: 'false'
|
||
pr_reviewer.require_tests_review: 'false'
|
||
pr_reviewer.require_security_review: 'false'
|
||
pr_reviewer.require_estimate_effort_to_review: 'false'
|
||
pr_reviewer.require_estimate_contribution_time_cost: 'false'
|
||
pr_reviewer.require_can_be_split_review: 'false'
|
||
pr_reviewer.require_todo_scan: 'false'
|
||
pr_reviewer.require_ticket_analysis_review: 'false'
|
||
pr_reviewer.enable_review_labels_effort: 'false'
|
||
pr_reviewer.enable_review_labels_security: 'false'
|
||
pr_reviewer.extra_instructions: |
|
||
Return key_issues_to_review only for concrete behavior defects introduced by this pull request.
|
||
Each finding must identify the affected behavior, a reachable trigger, and the existing contract or invariant it violates.
|
||
Use issue_content to state the smallest correction boundary, not a code patch.
|
||
Do not report style preferences, comments, refactors, architecture alternatives, speculative races, extra hardening, optional tests, or hypothetical concerns.
|
||
Return no findings when the evidence is incomplete.
|
||
|
||
- name: Publish review comments and summary
|
||
if: >-
|
||
steps.pr_context.outputs.skip_pr_agent != 'true' &&
|
||
steps.review_analysis.outcome == 'success' &&
|
||
(
|
||
github.event_name == 'pull_request_target' ||
|
||
github.event.comment.body == '/review' ||
|
||
startsWith(github.event.comment.body, '/review ')
|
||
)
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||
REVIEWED_HEAD_SHA: ${{ steps.pr_context.outputs.head_sha }}
|
||
CHANGED_FILES: ${{ steps.pr_context.outputs.changed_files }}
|
||
RESPONSE_LANGUAGE: ${{ steps.pr_context.outputs.response_language }}
|
||
REVIEW_JSON: ${{ steps.review_analysis.outputs.review }}
|
||
run: |
|
||
set -euo pipefail
|
||
current_head_sha="$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.head.sha')"
|
||
if [ "${current_head_sha}" != "${REVIEWED_HEAD_SHA}" ]; then
|
||
echo "PR head changed during analysis; skip stale review publication."
|
||
exit 0
|
||
fi
|
||
|
||
review_payload="$(mktemp)"
|
||
python3 - "${review_payload}" <<'PY'
|
||
import hashlib
|
||
import json
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
from urllib.parse import quote
|
||
|
||
review_raw = os.environ.get("REVIEW_JSON") or "{}"
|
||
review = json.loads(review_raw)
|
||
if not review_raw.strip() or review == {}:
|
||
if int(os.environ.get("CHANGED_FILES") or 0):
|
||
raise SystemExit("Review analysis produced no structured output for a non-empty PR.")
|
||
review = {}
|
||
|
||
repo = os.environ["REPO"]
|
||
number = os.environ["PR_NUMBER"]
|
||
head_sha = os.environ["REVIEWED_HEAD_SHA"]
|
||
language = os.environ.get("RESPONSE_LANGUAGE") or "zh-CN"
|
||
|
||
def paged(endpoint):
|
||
result = json.loads(subprocess.check_output(
|
||
["gh", "api", "--paginate", "--slurp", endpoint], text=True
|
||
))
|
||
if result and all(isinstance(page, list) for page in result):
|
||
return [item for page in result for item in page]
|
||
return result
|
||
|
||
files = paged(f"repos/{repo}/pulls/{number}/files?per_page=100")
|
||
comments = paged(f"repos/{repo}/pulls/{number}/comments?per_page=100")
|
||
reviews = paged(f"repos/{repo}/pulls/{number}/reviews?per_page=100")
|
||
|
||
hunk_pattern = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@")
|
||
changed_lines = {}
|
||
for file_data in files:
|
||
path = str(file_data.get("filename") or "")
|
||
line = None
|
||
lines = set()
|
||
for patch_line in (file_data.get("patch") or "").splitlines():
|
||
hunk = hunk_pattern.match(patch_line)
|
||
if hunk:
|
||
line = int(hunk.group(1))
|
||
continue
|
||
if line is None or patch_line.startswith("\\"):
|
||
continue
|
||
if patch_line.startswith("+") and not patch_line.startswith("+++"):
|
||
lines.add(line)
|
||
line += 1
|
||
elif patch_line.startswith("-") and not patch_line.startswith("---"):
|
||
continue
|
||
else:
|
||
line += 1
|
||
changed_lines[path] = lines
|
||
|
||
def fingerprint(path, line):
|
||
normalized = "\n".join((path, str(line)))
|
||
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:16]
|
||
|
||
current_fingerprints = set()
|
||
current_locations = set()
|
||
marker_pattern = re.compile(r"<!-- pr-agent-review:([0-9a-f]{16}) -->")
|
||
for comment in comments:
|
||
if comment.get("user", {}).get("login") != "github-actions[bot]":
|
||
continue
|
||
if comment.get("line") is None:
|
||
continue
|
||
match = marker_pattern.search(str(comment.get("body") or ""))
|
||
if match:
|
||
current_fingerprints.add(match.group(1))
|
||
path = str(comment.get("path") or "")
|
||
try:
|
||
line = int(comment.get("line") or 0)
|
||
except (TypeError, ValueError):
|
||
line = 0
|
||
if path and line > 0:
|
||
current_locations.add((path, line))
|
||
|
||
def code_url(path, line):
|
||
return f"https://github.com/{repo}/blob/{head_sha}/{quote(path, safe='/')}#L{line}"
|
||
|
||
issues = review.get("key_issues_to_review") or []
|
||
findings = []
|
||
seen = set()
|
||
for issue in issues:
|
||
if not isinstance(issue, dict):
|
||
continue
|
||
path = str(issue.get("relevant_file") or "").strip()
|
||
header = str(issue.get("issue_header") or "").strip()
|
||
content = str(issue.get("issue_content") or "").strip()
|
||
try:
|
||
line = int(issue.get("start_line") or 0)
|
||
except (TypeError, ValueError):
|
||
line = 0
|
||
if not path or not header or not content or line < 1:
|
||
continue
|
||
finding_key = (path, line, header.lower(), " ".join(content.split()).lower())
|
||
if finding_key in seen:
|
||
continue
|
||
seen.add(finding_key)
|
||
findings.append({
|
||
"path": path,
|
||
"line": line,
|
||
"header": header,
|
||
"content": content,
|
||
"fingerprint": fingerprint(path, line),
|
||
})
|
||
|
||
new_comments = []
|
||
for finding in findings:
|
||
if finding["line"] not in changed_lines.get(finding["path"], set()):
|
||
continue
|
||
if finding["fingerprint"] in current_fingerprints or (finding["path"], finding["line"]) in current_locations:
|
||
continue
|
||
new_comments.append({
|
||
"path": finding["path"],
|
||
"line": finding["line"],
|
||
"side": "RIGHT",
|
||
"body": "\n".join([
|
||
f"<!-- pr-agent-review:{finding['fingerprint']} -->",
|
||
f"**{finding['header']}**",
|
||
"",
|
||
finding["content"],
|
||
]),
|
||
})
|
||
|
||
marker = "<!-- pr-agent-review-summary -->"
|
||
short_sha = head_sha[:7]
|
||
commit_url = f"https://github.com/{repo}/commit/{head_sha}"
|
||
chinese = language == "zh-CN"
|
||
lines = [marker, "## PR-Agent Code Review", ""]
|
||
if findings:
|
||
for finding in findings:
|
||
location = f"{finding['path']}:{finding['line']}"
|
||
concise = " ".join(finding["content"].split())[:360]
|
||
separator = ":" if chinese else ":"
|
||
lines.append(f"- [{location}]({code_url(finding['path'], finding['line'])}){separator} **{finding['header']}** - {concise}")
|
||
elif chinese:
|
||
lines.append("本次变更无需提出审查意见,暂无其他反馈。")
|
||
else:
|
||
lines.append("There are no review comments for the current changes. I have no additional feedback to provide.")
|
||
lines.extend([
|
||
"",
|
||
f"审查提交:[{short_sha}]({commit_url})" if chinese else f"Reviewed commit: [{short_sha}]({commit_url})",
|
||
"",
|
||
])
|
||
payload = {
|
||
"body": "\n".join(lines),
|
||
"commit_id": head_sha,
|
||
"event": "COMMENT",
|
||
}
|
||
if new_comments:
|
||
payload["comments"] = new_comments
|
||
has_matching_summary = not new_comments and any(
|
||
existing.get("user", {}).get("login") == "github-actions[bot]"
|
||
and existing.get("commit_id") == head_sha
|
||
and str(existing.get("body") or "") == payload["body"]
|
||
for existing in reviews
|
||
)
|
||
# 同一提交的手工重审仍会完成分析;完全相同的结果不重复发布 Review。
|
||
if not has_matching_summary:
|
||
with open(sys.argv[1], "w", encoding="utf-8") as handle:
|
||
json.dump(payload, handle, ensure_ascii=False)
|
||
PY
|
||
|
||
latest_head_sha="$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.head.sha')"
|
||
if [ "${latest_head_sha}" != "${REVIEWED_HEAD_SHA}" ]; then
|
||
echo "PR head changed while rendering review; skip stale review publication."
|
||
exit 0
|
||
fi
|
||
|
||
if [ -s "${review_payload}" ]; then
|
||
gh api --method POST "repos/${REPO}/pulls/${PR_NUMBER}/reviews" --input "${review_payload}" >/dev/null
|
||
fi
|
||
old_summary_ids="$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments?per_page=100" --jq ".[] | select(.user.login == \"github-actions[bot]\" and ((.body | startswith(\"<!-- pr-agent-update-notification -->\")) or (.body | startswith(\"<!-- pr-agent-code-review-summary -->\")) or (.body | startswith(\"<!-- pr-agent-review-summary -->\")) or (.body | startswith(\"<!-- pr-agent-lab:review -->\")))) | .id")"
|
||
while IFS= read -r comment_id; do
|
||
[ -z "${comment_id}" ] && continue
|
||
gh api --method DELETE "repos/${REPO}/issues/comments/${comment_id}" >/dev/null
|
||
done <<< "${old_summary_ids}"
|
||
|
||
- name: Answer PR question
|
||
if: >-
|
||
steps.pr_context.outputs.skip_pr_agent != 'true' &&
|
||
github.event_name == 'issue_comment' &&
|
||
(
|
||
github.event.comment.body == '/ask' ||
|
||
startsWith(github.event.comment.body, '/ask ')
|
||
)
|
||
uses: docker://pragent/pr-agent:0.39.0-github_action@sha256:b253845caa8c7ff5ce8be78f32996647982bdd4890826a962b78eff2e385a825
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||
OPENAI.API_BASE: ${{ secrets.OPENAI_API_BASE }}
|
||
config.model: 'gpt-5.6-terra'
|
||
config.fallback_models: '["gpt-5.5", "gpt-5.4"]'
|
||
config.custom_model_max_tokens: '1050000'
|
||
config.reasoning_effort: 'high'
|
||
config.ai_timeout: '900'
|
||
config.response_language: ${{ steps.pr_context.outputs.response_language }}
|
||
config.large_patch_policy: 'clip'
|
||
config.ignore_pr_title: '["^\\[Auto\\]", "^Auto"]'
|
||
config.ignore_pr_labels: '["skip pr-agent"]'
|
||
github_action_config.auto_review: 'false'
|
||
github_action_config.auto_describe: 'false'
|
||
github_action_config.auto_improve: 'false'
|