Files
ai-team-dashboard/dashboard/03-任务脚本/poll-tasks.sh

399 lines
12 KiB
Bash
Raw Normal View History

#!/bin/bash
# GitHub 任务轮询 — 迭代循环模式
# 每次轮询只处理 checklist 中的一项,直到全部完成
# 完成后自动交接给大龙虾(项目经理)
REPO="$GITHUB_REPO"
BOT="$BOT_NAME"
ROLE="$BOT_ROLE"
CODE_REPO_DIR="/home/node/code-repo"
SKILLS_REPO_DIR="/home/node/skills-repo"
WORKSPACE="/home/node/.openclaw/workspace"
SKILLS_DIR="/home/node/.openclaw/skills"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "[$TIMESTAMP] $BOT 开始轮询任务..."
health=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/healthz 2>/dev/null)
if [ "$health" != "200" ]; then
echo "⚠️ Gateway 未就绪 (HTTP $health), 跳过本轮"
exit 0
fi
# ── 同步技能仓的新技能 ──
if [ -d "$SKILLS_REPO_DIR/.git" ]; then
cd "$SKILLS_REPO_DIR" && git pull --rebase 2>/dev/null
for f in "$SKILLS_REPO_DIR"/*.md; do
[ -f "$f" ] && cp "$f" "$SKILLS_DIR/" 2>/dev/null
done
fi
# ── 1. 先找 in-progress 的任务(上次没做完的) ──
issue_json=$(gh issue list -R "$REPO" -l "status:in-progress" -l "role:${BOT}" \
--json number,title,body -q '.[0]' 2>/dev/null)
# ── 2. 没有 in-progress 就找 pending 的 ──
if [ -z "$issue_json" ] || [ "$issue_json" = "null" ]; then
issue_json=$(gh issue list -R "$REPO" -l "status:pending" -l "role:${BOT}" \
--json number,title,body -q '.[0]' 2>/dev/null)
if [ -z "$issue_json" ] || [ "$issue_json" = "null" ]; then
echo "📭 没有待处理任务"
exit 0
fi
number=$(echo "$issue_json" | jq -r '.number')
gh issue edit "$number" -R "$REPO" \
--remove-label "status:pending" \
--add-label "status:in-progress" 2>/dev/null
echo "📋 领取新任务 #$number"
else
echo "🔄 继续处理进行中任务"
fi
number=$(echo "$issue_json" | jq -r '.number')
title=$(echo "$issue_json" | jq -r '.title')
body=$(echo "$issue_json" | jq -r '.body')
echo "📋 任务 #$number: $title"
# ── 3. 确定当前阶段,只解析对应 section 的 checklist ──
current_phase="unknown"
section_keyword=""
if echo "$body" | grep -q '\*\*phase:design\*\*'; then
current_phase="design"
section_keyword="需求清单"
elif echo "$body" | grep -q '\*\*phase:coding\*\*'; then
current_phase="coding"
section_keyword="编码清单"
elif echo "$body" | grep -q '\*\*phase:review\*\*'; then
current_phase="review"
section_keyword="测试清单"
fi
next_item=""
next_item_num=0
section_item_num=0
total_items=0
done_items=0
global_count=0
in_target_section=0
while IFS= read -r line; do
if echo "$line" | grep -qi "$section_keyword"; then
in_target_section=1
elif echo "$line" | grep -qE '^## |^---'; then
if [ $in_target_section -eq 1 ] && [ $total_items -gt 0 ]; then
in_target_section=0
fi
fi
if echo "$line" | grep -qE '^\- \[[ x]\] [0-9]+\.'; then
global_count=$((global_count + 1))
if [ $in_target_section -eq 1 ]; then
total_items=$((total_items + 1))
if echo "$line" | grep -qE '^\- \[x\]'; then
done_items=$((done_items + 1))
elif [ -z "$next_item" ]; then
next_item=$(echo "$line" | sed 's/^- \[ \] [0-9]*\.\s*//')
next_item_num=$global_count
section_item_num=$total_items
fi
fi
fi
done <<< "$body"
if [ $total_items -eq 0 ]; then
echo "⚠️ 当前阶段 ($current_phase) 没有 checklist按整体任务处理"
next_item="$body"
total_items=1
next_item_num=1
section_item_num=1
fi
if [ -z "$next_item" ] && [ $total_items -gt 0 ]; then
echo "✅ 当前阶段所有项目已完成 ($done_items/$total_items)"
phase_label="设计"
[ "$current_phase" = "coding" ] && phase_label="编码"
[ "$current_phase" = "review" ] && phase_label="测试"
echo "📌 ${phase_label}阶段全部完成,交接给大龙虾"
gh issue edit "$number" -R "$REPO" \
--remove-label "role:${BOT}" \
--add-label "role:dalongxia" 2>/dev/null
tmphandoff=$(mktemp /tmp/issue-handoff-XXXXXX.md)
cat > "$tmphandoff" << HANDOFF_EOF
🦞 **${phase_label}阶段全部完成** ($done_items/$total_items) — 交接给大龙虾
**执行者**: ${BOT}
**完成阶段**: ${current_phase}
**完成项数**: ${done_items}/${total_items}
请大龙虾(项目经理):
1. 审查本阶段成果
2. 分析整体项目进度
3. 决定下一步安排(编写下阶段清单、指派执行者)
HANDOFF_EOF
gh issue comment "$number" -R "$REPO" --body-file "$tmphandoff" 2>/dev/null
rm -f "$tmphandoff"
exit 0
fi
echo "🎯 处理第 $section_item_num/$total_items 项: $next_item"
tmpstart=$(mktemp /tmp/issue-start-XXXXXX.md)
cat > "$tmpstart" << START_EOF
🔄 **${BOT}** 开始处理第 **$section_item_num/$total_items** 项:
> $next_item
$TIMESTAMP
START_EOF
gh issue comment "$number" -R "$REPO" --body-file "$tmpstart" 2>/dev/null
rm -f "$tmpstart"
# ── 4. 构建 prompt 给 bot ──
phase_instruction=""
case "$current_phase" in
design)
phase_instruction="你当前处于【设计阶段】。请对以下需求进行详细的技术设计包括数据模型、API 接口设计、技术方案、架构说明。输出要具体、可执行。"
;;
coding)
phase_instruction="你当前处于【编码阶段】。请按照设计方案完成以下编码任务。代码必须完整、可运行、有注释。所有代码文件写到工作目录。"
;;
review)
phase_instruction="你当前处于【测试/审查阶段】。请检查以下测试条目,验证功能是否正确,输出测试报告。如果发现问题,详细说明问题位置和修复建议。"
;;
*)
phase_instruction="请认真完成以下任务。"
;;
esac
task_prompt="你是 ${BOT},角色是 ${ROLE}
## 当前任务
项目: ${title} (Issue #${number})
当前阶段: ${current_phase}
当前进度: ${done_items}/${total_items} 已完成
## 本次要处理的项目(第 ${section_item_num} 项)
${next_item}
## 工作指引
${phase_instruction}
## 项目完整背景
${body}
## 重要规则
1. 你只需处理上面【第 ${section_item_num} 项】,不要做其他项
2. 完成后给出完整的交付物
3. 如果遇到问题无法解决,说明具体卡在哪里
4. 如果学到了新技能,在 /home/node/.openclaw/skills/ 创建 .md 记录"
echo "🧠 发送任务到 AI..."
start_time=$(date +%s)
result=$(openclaw agent \
--message "$task_prompt" \
--session-id "github-task-$number-item-$next_item_num" \
--thinking medium \
--timeout 300 \
--json 2>&1) || true
end_time=$(date +%s)
duration=$(( end_time - start_time ))
response=$(echo "$result" | jq -r '.response // empty' 2>/dev/null)
if [ -z "$response" ]; then
response=$(echo "$result" | jq -r '.text // empty' 2>/dev/null)
fi
if [ -z "$response" ]; then
response=$(echo "$result" | jq -r '.content // empty' 2>/dev/null)
fi
if [ -z "$response" ]; then
response="$result"
fi
# ── 5. 代码推送 ──
commit_url=""
if [ -d "$CODE_REPO_DIR/.git" ]; then
task_dir="$CODE_REPO_DIR/task-${number}"
mkdir -p "$task_dir"
has_code=false
for f in "$WORKSPACE"/*.py "$WORKSPACE"/*.js "$WORKSPACE"/*.ts "$WORKSPACE"/*.html \
"$WORKSPACE"/*.css "$WORKSPACE"/*.json "$WORKSPACE"/*.go "$WORKSPACE"/*.rs \
"$WORKSPACE"/*.java "$WORKSPACE"/*.swift "$WORKSPACE"/*.sh "$WORKSPACE"/*.md \
"$WORKSPACE"/*.yaml "$WORKSPACE"/*.yml "$WORKSPACE"/*.toml "$WORKSPACE"/*.sql \
"$WORKSPACE"/*.jsx "$WORKSPACE"/*.tsx "$WORKSPACE"/*.vue "$WORKSPACE"/*.wxml \
"$WORKSPACE"/*.wxss; do
if [ -f "$f" ]; then
cp "$f" "$task_dir/" 2>/dev/null
has_code=true
fi
done
for d in "$WORKSPACE"/*/; do
if [ -d "$d" ] && [ "$(basename "$d")" != "." ]; then
dname=$(basename "$d")
case "$dname" in
node_modules|.git|__pycache__|.openclaw|.cache|plugins) continue ;;
esac
cp -r "$d" "$task_dir/" 2>/dev/null
has_code=true
fi
done
if [ "$has_code" = true ]; then
cd "$CODE_REPO_DIR"
git add -A 2>/dev/null
commit_msg="task-${number} item-${next_item_num}: ${next_item}
执行者: ${BOT} (${ROLE})
进度: ${next_item_num}/${total_items}
耗时: ${duration}s"
git commit -m "$commit_msg" 2>/dev/null
git push origin main 2>/dev/null && echo "📦 代码已推送"
commit_hash=$(git rev-parse --short HEAD 2>/dev/null)
commit_url="https://github.com/${GITHUB_OWNER}/${CODE_REPO}/commit/${commit_hash}"
fi
fi
# ── 6. 技能推送 ──
skills_url=""
if [ -d "$SKILLS_REPO_DIR/.git" ]; then
has_new_skills=false
for f in "$SKILLS_DIR"/*.md; do
if [ -f "$f" ]; then
fname=$(basename "$f")
if [ "$fname" = "github-task-worker.md" ]; then continue; fi
if [ ! -f "$SKILLS_REPO_DIR/$fname" ] || ! diff -q "$f" "$SKILLS_REPO_DIR/$fname" >/dev/null 2>&1; then
cp "$f" "$SKILLS_REPO_DIR/" 2>/dev/null
has_new_skills=true
fi
fi
done
if [ "$has_new_skills" = true ]; then
cd "$SKILLS_REPO_DIR"
git add -A 2>/dev/null
git commit -m "skill: 新增/更新技能 (task-${number} item-${next_item_num})" 2>/dev/null
git push origin main 2>/dev/null && echo "🧠 技能已推送"
skill_hash=$(git rev-parse --short HEAD 2>/dev/null)
skills_url="https://github.com/${GITHUB_OWNER}/${SKILLS_REPO}/commit/${skill_hash}"
fi
fi
# ── 7. 更新 Issue body勾选已完成项 + 更新当前阶段进度 ──
new_done=$((done_items + 1))
updated_body=$(echo "$body" | awk -v item_num="$next_item_num" '
BEGIN { count = 0 }
/^- \[[ x]\] [0-9]+\./ {
count++
if (count == item_num) {
sub(/^- \[ \]/, "- [x]")
}
}
{ print }
')
if [ "$current_phase" = "design" ]; then
updated_body=$(echo "$updated_body" | sed "s/设计: [0-9]*\/[0-9]*/设计: ${new_done}\/${total_items}/")
elif [ "$current_phase" = "coding" ]; then
updated_body=$(echo "$updated_body" | sed "s/编码: [0-9]*\/[0-9]*/编码: ${new_done}\/${total_items}/")
elif [ "$current_phase" = "review" ]; then
updated_body=$(echo "$updated_body" | sed "s/测试: [0-9]*\/[0-9]*/测试: ${new_done}\/${total_items}/")
fi
tmpbody=$(mktemp /tmp/issue-body-XXXXXX.md)
printf '%s' "$updated_body" > "$tmpbody"
gh issue edit "$number" -R "$REPO" --body-file "$tmpbody" 2>/dev/null
edit_rc=$?
rm -f "$tmpbody"
if [ $edit_rc -ne 0 ]; then
echo "⚠️ Issue body 更新失败 (rc=$edit_rc)"
fi
echo "📝 已勾选第 $section_item_num 项,进度: $new_done/$total_items"
# ── 8. 评论交付结果 ──
max_len=60000
if [ ${#response} -gt $max_len ]; then
response="${response:0:$max_len}
...(输出过长,已截断)"
fi
finish_time=$(date '+%Y-%m-%d %H:%M:%S')
extra_section=""
if [ -n "$commit_url" ]; then
extra_section="
**📦 代码已提交**: [查看 commit](${commit_url})"
fi
if [ -n "$skills_url" ]; then
extra_section="${extra_section}
**🧠 技能已更新**: [查看 commit](${skills_url})"
fi
tmpcomment=$(mktemp /tmp/issue-comment-XXXXXX.md)
cat > "$tmpcomment" << COMMENT_EOF
## ✅ 第 ${section_item_num}/${total_items} 项完成
**执行者**: ${BOT} (${ROLE})
**完成项**: ${next_item}
**进度**: ${new_done}/${total_items}
**耗时**: ${duration}
**完成时间**: ${finish_time}${extra_section}
---
${response}
COMMENT_EOF
gh issue comment "$number" -R "$REPO" --body-file "$tmpcomment" 2>/dev/null
comment_rc=$?
rm -f "$tmpcomment"
if [ $comment_rc -ne 0 ]; then
echo "⚠️ 完成评论发布失败 (rc=$comment_rc)"
fi
# ── 9. 判断是否全部完成 → 交接给大龙虾 ──
if [ $new_done -ge $total_items ]; then
echo "🎉 当前阶段所有项目已完成!交接给大龙虾"
phase_label="设计"
[ "$current_phase" = "coding" ] && phase_label="编码"
[ "$current_phase" = "review" ] && phase_label="测试"
gh issue edit "$number" -R "$REPO" \
--remove-label "role:${BOT}" \
--add-label "role:dalongxia" 2>/dev/null
tmphandoff=$(mktemp /tmp/issue-handoff-XXXXXX.md)
cat > "$tmphandoff" << HANDOFF_EOF
🦞 **${phase_label}阶段全部完成** ($total_items/$total_items) — 交接给大龙虾
**执行者**: ${BOT}
**完成阶段**: ${current_phase}
**完成项数**: ${total_items}/${total_items}
请大龙虾(项目经理):
1. 审查本阶段成果
2. 分析整体项目进度
3. 决定下一步安排(编写下阶段清单、指派执行者)
HANDOFF_EOF
gh issue comment "$number" -R "$REPO" --body-file "$tmphandoff" 2>/dev/null
rm -f "$tmphandoff"
else
echo "📌 还剩 $((total_items - new_done)) 项未完成,等待下次轮询"
fi
echo "✅ 第 $section_item_num/$total_items 项处理完成 (耗时 ${duration}s)"