Skip to content

[文献] 物种形成与杂交领域新文献 - 2026-04-10 #21425

[文献] 物种形成与杂交领域新文献 - 2026-04-10

[文献] 物种形成与杂交领域新文献 - 2026-04-10 #21425

Workflow file for this run

name: Issue Orchestrator
# 支持三种触发方式:@mention、/command、标签变更
on:
issue_comment:
types: [created, edited]
issues:
types: [labeled]
# 并发控制:允许多个触发共存,每个触发独立运行
concurrency:
group: ${{ github.event.issue.number }}-${{ github.run_id }}
cancel-in-progress: false
permissions:
issues: write
contents: read
jobs:
# ========== 标签触发:state:ready-for-review ==========
auto-trigger-review:
if: github.event_name == 'issues' &&
github.event.action == 'labeled' &&
github.event.label.name == 'state:ready-for-review' &&
github.event.issue.state == 'open'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
prune-cache: false
- run: uv sync --extra video
- name: Update issue state
run: |
gh issue edit ${{ github.event.issue.number }} \
--add-label "state:review" \
--remove-label "state:ready-for-review" \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
- name: Run full review process
run: |
echo "Running review flow..."
uv run python -m issuelab review \
--issue ${{ github.event.issue.number }} \
--post
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
MINIMAX_API_KEY: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
LOG_FILE: ${{ github.workspace }}/logs/auto_review_${{ github.event.issue.number }}.log
LOG_LEVEL: DEBUG
CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK: "true"
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "30000"
ACTIONS_STEP_DEBUG: "true"
ACTIONS_RUNNER_DEBUG: "true"
- name: Mark needs summary
if: always()
run: |
gh issue edit ${{ github.event.issue.number }} \
--add-label "bot:needs-summary" \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
- uses: actions/upload-artifact@v4
with:
name: auto-review-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
# ========== @mention 触发:检测并分发 ==========
detect-mentions:
if: |
github.event_name == 'issue_comment' &&
github.event.issue.state == 'open' &&
github.event.comment.user.type != 'Bot' &&
!contains(fromJson('["moderator","reviewer_a","reviewer_b","summarizer","observer","pubmed_observer","arxiv_observer","video_manim"]'), github.event.comment.user.login) &&
contains(github.event.comment.body, '@')
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
agents: ${{ steps.detect.outputs.agents }}
has_system: ${{ steps.detect.outputs.has_system }}
user_mentions: ${{ steps.detect.outputs.user_mentions }}
has_user: ${{ steps.detect.outputs.has_user }}
mention_source: ${{ steps.detect.outputs.mention_source }}
detect_reason: ${{ steps.detect.outputs.detect_reason }}
steps:
- uses: actions/checkout@v4
- name: Detect mentions
id: detect
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
DETECT_OUTPUT=$(python - <<'PY'
import json
import os
import sys
from pathlib import Path
src_path = Path.cwd() / "src"
if src_path.exists():
sys.path.insert(0, str(src_path))
from issuelab.agents.registry import load_registry
from issuelab.utils.mentions import extract_controlled_mentions, extract_github_mentions
comment = os.environ.get("COMMENT_BODY", "")
is_agent_comment = "[Agent:" in comment
controlled_mentions = extract_controlled_mentions(comment)
mention_source = "controlled"
mentions = controlled_mentions
if not mentions:
mention_source = "body_fallback"
mentions = extract_github_mentions(comment)
if not mentions:
mention_source = "none"
registry = load_registry(Path("agents"))
system_agents: set[str] = set()
user_agents: set[str] = set()
for username, config in registry.items():
candidate = str(username).strip().lower()
if not candidate:
continue
agent_type = str(config.get("agent_type", "")).strip().lower()
if agent_type == "system":
system_agents.add(candidate)
else:
user_agents.add(candidate)
found_system: list[str] = []
found_user: list[str] = []
for mention in mentions:
candidate = mention.lower()
# agent 评论不再触发 system agents,避免递归链路;
# 但允许触发 user agents(如 @gqy20)。
if (not is_agent_comment) and candidate in system_agents and candidate not in found_system:
found_system.append(candidate)
if candidate in user_agents and candidate not in found_user:
found_user.append(candidate)
if found_system or found_user:
detect_reason = "matched_registered_agents"
elif mention_source == "controlled":
detect_reason = "controlled_mentions_not_registered"
elif mention_source == "body_fallback":
detect_reason = "body_mentions_not_registered"
else:
detect_reason = "no_mentions_detected"
print(
json.dumps(
{
"system": ",".join(found_system),
"user": ",".join(found_user),
"mention_source": mention_source,
"detect_reason": detect_reason,
},
ensure_ascii=False,
),
end="",
)
PY
)
FOUND_AGENTS=$(echo "$DETECT_OUTPUT" | python -c 'import json,sys; print((json.load(sys.stdin)).get("system",""), end="")')
USER_MENTIONS=$(echo "$DETECT_OUTPUT" | python -c 'import json,sys; print((json.load(sys.stdin)).get("user",""), end="")')
MENTION_SOURCE=$(echo "$DETECT_OUTPUT" | python -c 'import json,sys; print((json.load(sys.stdin)).get("mention_source",""), end="")')
DETECT_REASON=$(echo "$DETECT_OUTPUT" | python -c 'import json,sys; print((json.load(sys.stdin)).get("detect_reason",""), end="")')
echo "mention_source=$MENTION_SOURCE" >> $GITHUB_OUTPUT
echo "detect_reason=$DETECT_REASON" >> $GITHUB_OUTPUT
echo "Mention source: $MENTION_SOURCE"
echo "Detect reason: $DETECT_REASON"
if [ -n "$FOUND_AGENTS" ]; then
echo "has_system=true" >> $GITHUB_OUTPUT
echo "agents=$FOUND_AGENTS" >> $GITHUB_OUTPUT
echo "Found system agents: $FOUND_AGENTS"
else
echo "has_system=false" >> $GITHUB_OUTPUT
echo "No system agents found"
fi
if [ -n "$USER_MENTIONS" ]; then
echo "has_user=true" >> $GITHUB_OUTPUT
echo "user_mentions=$USER_MENTIONS" >> $GITHUB_OUTPUT
echo "Found user agents: $USER_MENTIONS"
else
echo "has_user=false" >> $GITHUB_OUTPUT
echo "No user agents found"
fi
dispatch-user-mentions:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_user == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- name: Dispatch user mentions
id: dispatch_user
env:
GITHUB_APP_ID: ${{ secrets.ISSUELAB_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.ISSUELAB_APP_PRIVATE_KEY }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY_JSON: ${{ toJson(github.event.issue.body) }}
COMMENT_BODY_JSON: ${{ toJson(github.event.comment.body) }}
run: |
ISSUE_BODY_FILE=$(mktemp)
COMMENT_BODY_FILE=$(mktemp)
export ISSUE_BODY_FILE
export COMMENT_BODY_FILE
python -c "import json, os; from pathlib import Path; issue=json.loads(os.environ.get('ISSUE_BODY_JSON','\"\"')); comment=json.loads(os.environ.get('COMMENT_BODY_JSON','\"\"')); Path(os.environ['ISSUE_BODY_FILE']).write_text(issue or '', encoding='utf-8'); Path(os.environ['COMMENT_BODY_FILE']).write_text(comment or '', encoding='utf-8')"
uv run python -m issuelab.cli.dispatch \
--mentions "${{ needs.detect-mentions.outputs.user_mentions }}" \
--agents-dir agents \
--source-repo "${{ github.repository }}" \
--issue-number ${{ github.event.issue.number }} \
--issue-title "$ISSUE_TITLE" \
--issue-body-file "$ISSUE_BODY_FILE" \
--comment-id ${{ github.event.comment.id }} \
--comment-body-file "$COMMENT_BODY_FILE" \
--labels '${{ toJson(github.event.issue.labels.*.name) }}' \
--app-id "$GITHUB_APP_ID" \
--app-private-key "$GITHUB_APP_PRIVATE_KEY"
rm -f "$ISSUE_BODY_FILE" "$COMMENT_BODY_FILE"
- name: Run local user agents
id: run_local_user
if: steps.dispatch_user.outputs.local_agents != '[]' && steps.dispatch_user.outputs.local_agents != ''
run: |
set +e
LOCAL_AGENTS='${{ steps.dispatch_user.outputs.local_agents }}'
echo "Running local user agents: $LOCAL_AGENTS"
local_total=$(echo "$LOCAL_AGENTS" | jq 'length')
local_success=0
local_failed=0
while read -r agent; do
echo "[LOCAL] Executing user agent: $agent"
MCP_EXPORTS=$(uv run python scripts/prepare_mcp_env.py --agent "$agent" --include-root-mcp --shell)
if [ -n "$MCP_EXPORTS" ]; then
eval "$MCP_EXPORTS"
fi
uv run python -m issuelab execute \
--issue ${{ github.event.issue.number }} \
--agents "$agent" \
--post
code=$?
if [ $code -eq 0 ]; then
local_success=$((local_success+1))
else
local_failed=$((local_failed+1))
echo "[WARNING] Failed to run local user agent: $agent"
fi
done < <(echo "$LOCAL_AGENTS" | jq -r '.[]')
{
echo "total=$local_total"
echo "success=$local_success"
echo "failed=$local_failed"
} >> "$GITHUB_OUTPUT"
set -e
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
LOG_FILE: ${{ github.workspace }}/logs/local_user_agents_${{ github.event.issue.number }}.log
LOG_LEVEL: DEBUG
CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK: "true"
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "30000"
MCP_LOG_DETAIL: "1"
PROMPT_LOG: "1"
ISSUELAB_TRIGGER_COMMENT: ${{ github.event.comment.body }}
ISSUELAB_MCP_ENV_JSON: ${{ secrets.ISSUELAB_MCP_ENV_JSON }}
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
ZOTERO_API_KEY: ${{ secrets.ZOTERO_API_KEY }}
ZOTERO_LIBRARY_ID: ${{ secrets.ZOTERO_LIBRARY_ID }}
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
SERPAPI_API_KEY: ${{ secrets.SERPAPI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# ========== 运行系统 Agent(并行 Job) ==========
run-moderator:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'moderator')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: moderator
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- uses: actions/upload-artifact@v4
with:
name: moderator-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-reviewer_a:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'reviewer_a')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: reviewer_a
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- uses: actions/upload-artifact@v4
with:
name: reviewer-a-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-reviewer_b:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'reviewer_b')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: reviewer_b
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- uses: actions/upload-artifact@v4
with:
name: reviewer-b-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-summarizer:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'summarizer')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: summarizer
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- name: Check for auto-close
if: always()
run: |
LAST_COMMENT=$(gh issue comments ${{ github.event.issue.number }} --repo ${{ github.repository }} --limit 1 --json body -q '.[0].body' 2>/dev/null || echo "")
if echo "$LAST_COMMENT" | grep -q '\[CLOSE\]'; then
echo "[INFO] 检测到 [CLOSE] 标记,正在关闭 Issue..."
gh issue close ${{ github.event.issue.number }} --repo ${{ github.repository }} --reason "completed"
echo "[OK] Issue 已自动关闭"
else
echo "[INFO] 未检测到 [CLOSE] 标记,不自动关闭"
fi
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
- uses: actions/upload-artifact@v4
with:
name: summarizer-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-pubmed-observer:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'pubmed_observer')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: pubmed_observer
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- uses: actions/upload-artifact@v4
with:
name: pubmed-observer-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-arxiv-observer:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'arxiv_observer')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: arxiv_observer
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- uses: actions/upload-artifact@v4
with:
name: arxiv-observer-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-observer:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'observer')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- uses: ./.github/actions/run-agent
with:
agent-name: observer
issue-number: ${{ github.event.issue.number }}
gh-token: ${{ secrets.PAT_TOKEN }}
anthropic-auth-token: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
anthropic-base-url: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
anthropic-model: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
- uses: actions/upload-artifact@v4
with:
name: observer-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
run-video-manim:
needs: detect-mentions
if: needs.detect-mentions.outputs.has_system == 'true' && contains(needs.detect-mentions.outputs.agents, 'video_manim')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- name: Install manim runtime dependencies
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg libcairo2-dev libpango1.0-dev pkg-config
- name: Run video_manim agent
run: |
mkdir -p outputs/manim
uv run python -m issuelab execute \
--issue ${{ github.event.issue.number }} \
--agents video_manim \
--post
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
LOG_FILE: ${{ github.workspace }}/logs/video_manim_${{ github.event.issue.number }}.log
LOG_LEVEL: DEBUG
CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK: "true"
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "30000"
ACTIONS_STEP_DEBUG: "true"
ACTIONS_RUNNER_DEBUG: "true"
MCP_LOG_DETAIL: "1"
PROMPT_LOG: "1"
ISSUELAB_TRIGGER_COMMENT: ${{ github.event.comment.body }}
- name: Upload video outputs
run: |
echo "[INFO] Listing candidate output directories"
ls -la agents/video_manim/outputs 2>/dev/null || true
ls -la agents/video_manim/media/videos 2>/dev/null || true
ls -la outputs 2>/dev/null || true
ls -la media/videos 2>/dev/null || true
find agents/video_manim -type f -name "*.mp4" 2>/dev/null | head -20 || true
find . -type f -name "*.mp4" 2>/dev/null | head -20 || true
- name: Upload video outputs artifact
uses: actions/upload-artifact@v4
with:
name: video-manim-outputs-${{ github.event.issue.number }}-${{ github.run_id }}
path: |
agents/video_manim/outputs/**
agents/video_manim/media/videos/**
outputs/manim/**
media/videos/**
if-no-files-found: warn
retention-days: 14
- name: Upload logs
uses: actions/upload-artifact@v4
with:
name: video-manim-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 14
- name: Comment artifact link
if: always()
run: |
gh issue comment ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--body "🎬 video_manim 已执行完成。请在本次 workflow 的 Artifacts 查看视频与脚本:https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
# ========== /command 触发(/review, /quiet, /close, /confirm-close) ==========
process-command:
if: github.event_name == 'issue_comment' &&
github.event.issue.state == 'open' &&
(contains(github.event.comment.body, '/review') ||
contains(github.event.comment.body, '/quiet') ||
contains(github.event.comment.body, '/close') ||
contains(github.event.comment.body, '/confirm-close')) &&
!contains(github.event.comment.body, '@')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
with:
python-version: '3.13'
enable-cache: true
- run: uv sync
- name: Extract command
id: extract
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
COMMENT="$COMMENT_BODY"
if echo "$COMMENT" | grep -q '/confirm-close'; then
echo "command=/confirm-close" >> $GITHUB_OUTPUT
elif echo "$COMMENT" | grep -q '/review'; then
echo "command=/review" >> $GITHUB_OUTPUT
elif echo "$COMMENT" | grep -q '/quiet'; then
echo "command=/quiet" >> $GITHUB_OUTPUT
elif echo "$COMMENT" | grep -q '/close'; then
echo "command=/close" >> $GITHUB_OUTPUT
fi
- name: Handle quiet command
if: steps.extract.outputs.command == '/quiet'
run: |
gh issue edit ${{ github.event.issue.number }} \
--add-label "bot:quiet" \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
- name: Request close confirmation
if: steps.extract.outputs.command == '/close'
run: |
echo "[INFO] 收到关闭 Issue 请求,需要确认..."
gh issue edit ${{ github.event.issue.number }} \
--add-label "bot:pending-close" \
--repo ${{ github.repository }}
gh issue comment ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--body - <<-EOF
## 关闭确认
此 Issue 即将被关闭。请回复 **/confirm-close** 以确认关闭,或任意回复以取消。
> 由 @${{ github.event.comment.user.login }} 发起关闭请求
EOF
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
- name: Confirm and close issue
if: steps.extract.outputs.command == '/confirm-close'
run: |
echo "[OK] 确认关闭 Issue..."
gh issue edit ${{ github.event.issue.number }} \
--remove-label "bot:pending-close" \
--repo ${{ github.repository }}
gh issue close ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--reason "completed"
gh issue comment ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--body "[OK] Issue 已由 @${{ github.event.comment.user.login }} 手动关闭"
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
- name: Run review process
if: steps.extract.outputs.command == '/review'
run: |
echo "Running /review command..."
gh issue edit ${{ github.event.issue.number }} \
--add-label "state:review" \
--repo ${{ github.repository }}
uv run python -m issuelab review \
--issue ${{ github.event.issue.number }} \
--post
gh issue edit ${{ github.event.issue.number }} \
--add-label "bot:needs-summary" \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL || 'https://api.minimaxi.com/anthropic' }}
ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL || 'MiniMax-M2.1' }}
LOG_FILE: ${{ github.workspace }}/logs/review_${{ github.event.issue.number }}.log
LOG_LEVEL: DEBUG
CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK: "true"
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "30000"
ACTIONS_STEP_DEBUG: "true"
ACTIONS_RUNNER_DEBUG: "true"
- uses: actions/upload-artifact@v4
with:
name: command-logs-${{ github.event.issue.number }}
path: logs/
retention-days: 7
observability-summary:
if: |
always() &&
(
needs.auto-trigger-review.result != 'skipped' ||
needs.detect-mentions.result != 'skipped' ||
needs.process-command.result != 'skipped'
)
needs:
- auto-trigger-review
- detect-mentions
- run-moderator
- run-reviewer_a
- run-reviewer_b
- run-summarizer
- run-pubmed-observer
- run-arxiv-observer
- run-observer
- run-video-manim
- dispatch-user-mentions
- process-command
runs-on: ubuntu-latest
steps:
- name: Emit observability summary
env:
WF_NAME: ${{ github.workflow }}
JOB_NAME: observability-summary
RUN_ID: ${{ github.run_id }}
REPO_NAME: ${{ github.repository }}
SHA: ${{ github.sha }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
NEEDS_JSON: ${{ toJson(needs) }}
SYSTEM_AGENTS: ${{ needs.detect-mentions.outputs.agents }}
HAS_SYSTEM: ${{ needs.detect-mentions.outputs.has_system }}
MENTION_SOURCE: ${{ needs.detect-mentions.outputs.mention_source }}
DETECT_REASON: ${{ needs.detect-mentions.outputs.detect_reason }}
run: |
set -euo pipefail
mkdir -p artifacts/observability
python - << 'PY'
import json
import os
from collections import Counter
from datetime import UTC, datetime
from pathlib import Path
needs = json.loads(os.getenv("NEEDS_JSON") or "{}")
results = {}
for job, payload in needs.items():
if isinstance(payload, dict):
results[job] = str(payload.get("result", "unknown"))
else:
results[job] = "unknown"
success_count = sum(1 for r in results.values() if r == "success")
failed_count = sum(1 for r in results.values() if r == "failure")
skipped_count = sum(1 for r in results.values() if r == "skipped")
input_count = len([j for j in results.keys() if j != "observability-summary"])
failures = Counter()
for job, result in results.items():
if result == "failure":
failures[f"JOB_FAILED:{job}"] += 1
elif result == "cancelled":
failures[f"JOB_CANCELLED:{job}"] += 1
failures_topn = [{"code": k, "count": v, "samples": []} for k, v in failures.most_common(8)]
status = "success"
if failed_count > 0 and success_count > 0:
status = "partial"
elif failed_count > 0 and success_count == 0:
status = "failure"
elif success_count == 0 and skipped_count > 0:
status = "skipped"
payload = {
"schema_version": "v1",
"workflow": os.getenv("WF_NAME"),
"job": os.getenv("JOB_NAME"),
"run_id": os.getenv("RUN_ID"),
"repo": os.getenv("REPO_NAME"),
"sha": os.getenv("SHA"),
"issue_number": os.getenv("ISSUE_NUMBER"),
"finished_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
"metrics": {
"input_count": input_count,
"success_count": success_count,
"failed_count": failed_count,
"skipped_count": skipped_count,
},
"context": {
"has_system_mentions": os.getenv("HAS_SYSTEM") or "",
"system_agents": os.getenv("SYSTEM_AGENTS") or "",
"mention_source": os.getenv("MENTION_SOURCE") or "",
"detect_reason": os.getenv("DETECT_REASON") or "",
"job_results": results,
},
"failures_topn": failures_topn,
"status": status,
}
Path("artifacts/observability/orchestrator__summary.json").write_text(
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
)
summary = [
"## Observability (orchestrator summary)",
"",
"| metric | value |",
"|---|---:|",
f"| input_count | {input_count} |",
f"| success_count | {success_count} |",
f"| failed_count | {failed_count} |",
f"| skipped_count | {skipped_count} |",
f"| status | {status} |",
"",
"### Failure TopN",
]
if failures_topn:
for item in failures_topn:
summary.append(f"- `{item['code']}`: {item['count']}")
else:
summary.append("- none")
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as f:
f.write("\n".join(summary) + "\n")
PY
- name: Upload observability artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: orchestrator-observability-${{ github.event.issue.number }}-${{ github.run_id }}
path: artifacts/observability/
retention-days: 14