[文献] 物种形成与杂交领域新文献 - 2026-04-10 #21417
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |