|
|
|
@@ -23,8 +23,15 @@ git config user.name "$NAME"
|
|
|
|
|
git config user.email "$NAME@ffaerber.duckdns.org"
|
|
|
|
|
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
|
|
|
hdr=(-H "Authorization: token $TOK" -H "Content-Type: application/json")
|
|
|
|
|
# Hidden loop-prevention marker appended to every agent REPLY/STATUS comment. Gitea already shows
|
|
|
|
|
# who authored a comment, so we don't repeat the agent's name in the body; but the trigger gate keys
|
|
|
|
|
# on the '🤖' character to know "this is an agent's own comment, don't fire a new run". An HTML
|
|
|
|
|
# comment renders as nothing, so the marker is invisible while still tripping the gate's guard.
|
|
|
|
|
# NOTE: trigger comments (delegation / autopilot / bounce) are posted with inline curl, NOT post()/
|
|
|
|
|
# prpost(), so they never get this marker and therefore DO fire the next run — that is intended.
|
|
|
|
|
MARK=$'\n\n<!-- 🤖 agent reply — do not trigger -->'
|
|
|
|
|
post() { curl -sS -w 'comment -> HTTP %{http_code}\n' -X POST "${hdr[@]}" \
|
|
|
|
|
"$API/issues/$NUM/comments" -d "$(jq -nc --arg b "$1" '{body:$b}')"; }
|
|
|
|
|
"$API/issues/$NUM/comments" -d "$(jq -nc --arg b "$1$MARK" '{body:$b}')"; }
|
|
|
|
|
# Remove the 'autopilot' label from an issue by resolving its ID first (Gitea's DELETE label
|
|
|
|
|
# endpoint is by ID, not name). Arg $1 = issue number. Used as the autopilot kill switch.
|
|
|
|
|
del_autopilot_label() {
|
|
|
|
@@ -54,12 +61,15 @@ reply=$(awk '
|
|
|
|
|
p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=0}; next }
|
|
|
|
|
{print}
|
|
|
|
|
' /tmp/agent_out.md 2>/dev/null)
|
|
|
|
|
# Strip a leading self-header the model sometimes emits ("🤖 **@pm**" on its own line) so we don't
|
|
|
|
|
# double it when we prepend our own. Removes a leading run of such header lines and blank lines.
|
|
|
|
|
reply=$(printf '%s' "$reply" | awk '
|
|
|
|
|
# Strip a leading self-identification header the model sometimes emits, e.g. "🤖 **@pm**",
|
|
|
|
|
# "🔨 **@senior**", or a heading like "## 🔨 @senior — <title>". Gitea already attributes the comment
|
|
|
|
|
# to its author, so we drop any leading line that references the agent's OWN @handle — or a bare
|
|
|
|
|
# "**@name**" line — together with surrounding blank lines, up to the first real content line.
|
|
|
|
|
reply=$(printf '%s' "$reply" | awk -v me="@$NAME" '
|
|
|
|
|
BEGIN{s=1}
|
|
|
|
|
s && /^[^A-Za-z0-9]*\*\*@[A-Za-z]+\*\*[[:space:]]*$/ {next}
|
|
|
|
|
s && /^[[:space:]]*$/ {next}
|
|
|
|
|
s && index($0, me) {next}
|
|
|
|
|
s && /^[^A-Za-z0-9]*\*\*@[A-Za-z]+\*\*[[:space:]]*$/ {next}
|
|
|
|
|
{s=0; print}
|
|
|
|
|
')
|
|
|
|
|
[ -z "$reply" ] && reply="_(Made changes without a text summary — see the diff below.)_"
|
|
|
|
@@ -113,9 +123,9 @@ if [ "$MODE" != "pr" ]; then
|
|
|
|
|
echo "created sub-issue #${n:-?}: $title"
|
|
|
|
|
[ -n "$n" ] && links="$links\n- #$n — $title"
|
|
|
|
|
done < /tmp/subtasks.txt
|
|
|
|
|
subtext=$(printf '\n\n---\n🤖 **@%s** — created sub-issues%s (mention an agent on each when ready):%b' "$NAME" "${ms:+ under milestone **$ms**}" "$links")
|
|
|
|
|
subtext=$(printf '\n\n---\nCreated sub-issues%s (mention an agent on each when ready):%b' "${ms:+ under milestone **$ms**}" "$links")
|
|
|
|
|
fi
|
|
|
|
|
post "$(printf '🤖 **@%s**\n\n%s%s' "$NAME" "$msg" "$subtext")"
|
|
|
|
|
post "$(printf '%s%s' "$msg" "$subtext")"
|
|
|
|
|
|
|
|
|
|
# --- AUTOPILOT: @qa's narrow, label-gated merge / halt authority ---
|
|
|
|
|
# Only @qa, only when 'autopilot' is set, and only on a PR thread. The MERGE_PR / HALT_AUTOPILOT
|
|
|
|
@@ -141,13 +151,13 @@ if [ "$MODE" != "pr" ]; then
|
|
|
|
|
echo "closing origin issue #${ISSNUM:-$NUM}"
|
|
|
|
|
curl -sS -X PATCH "${hdr[@]}" "$API/issues/${ISSNUM:-$NUM}" \
|
|
|
|
|
-d '{"state":"closed"}' -w '\nclose -> HTTP %{http_code}\n' || true
|
|
|
|
|
post "$(printf '🤖 **@qa** — ✅ verified & merged PR #%s (autopilot). Closed issue #%s.' "$NUM" "${ISSNUM:-$NUM}")"
|
|
|
|
|
post "$(printf '✅ Verified & merged PR #%s (autopilot). Closed issue #%s.' "$NUM" "${ISSNUM:-$NUM}")"
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
# Merge failed (checks not green, conflicts, or TOKEN_QA lacks merge scope) — do NOT
|
|
|
|
|
# silently proceed: drop the label so it reverts to human control and report.
|
|
|
|
|
del_autopilot_label "${ISSNUM:-$NUM}"
|
|
|
|
|
post "$(printf '🤖 **@qa** — ⚠️ tried to merge PR #%s but the API returned HTTP %s (checks not green, a conflict, or missing merge permission on TOKEN_QA). Removed the `autopilot` label — @ffaerber please take a look.' "$NUM" "$mc")"
|
|
|
|
|
post "$(printf '⚠️ Tried to merge PR #%s but the API returned HTTP %s (checks not green, a conflict, or missing merge permission on TOKEN_QA). Removed the `autopilot` label — @ffaerber please take a look.' "$NUM" "$mc")"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
fi
|
|
|
|
@@ -167,7 +177,7 @@ if [ "$MODE" != "pr" ]; then
|
|
|
|
|
if [ "$prior" -ge 3 ]; then
|
|
|
|
|
echo "@qa autopilot: 3 bounces already — halting"
|
|
|
|
|
del_autopilot_label "${ISSNUM:-$NUM}"
|
|
|
|
|
post "$(printf '🤖 **@qa** — 🛑 still not right after 3 fix attempts. Stopping autopilot (removed the `autopilot` label). @ffaerber please take over — details in the comments above.')"
|
|
|
|
|
post "$(printf '🛑 Still not right after 3 fix attempts. Stopping autopilot (removed the `autopilot` label). @ffaerber please take over — details in the comments above.')"
|
|
|
|
|
else
|
|
|
|
|
n=$((prior + 1))
|
|
|
|
|
echo "@qa autopilot: bounce $n/3 -> @$target"
|
|
|
|
@@ -180,7 +190,7 @@ if [ "$MODE" != "pr" ]; then
|
|
|
|
|
elif grep -qiE '^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$' /tmp/agent_out.md; then
|
|
|
|
|
echo "@qa autopilot: HALT — removing 'autopilot' label from #${ISSNUM:-$NUM}"
|
|
|
|
|
del_autopilot_label "${ISSNUM:-$NUM}"
|
|
|
|
|
post "$(printf '🤖 **@qa** — 🛑 this needs a human decision (not a dev fix). Removed the `autopilot` label (back to human control). @ffaerber please decide next steps (details above).')"
|
|
|
|
|
post "$(printf '🛑 This needs a human decision (not a dev fix). Removed the `autopilot` label (back to human control). @ffaerber please decide next steps (details above).')"
|
|
|
|
|
fi
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
@@ -228,7 +238,7 @@ fi
|
|
|
|
|
git push origin "HEAD:$BRANCH" || true
|
|
|
|
|
git fetch -q origin 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
prbody=$(printf '%s\n\n---\nResolves #%s · 🤖 @%s' "$prdesc" "$NUM" "$NAME")
|
|
|
|
|
prbody=$(printf '%s\n\n---\nResolves #%s' "$prdesc" "$NUM")
|
|
|
|
|
owner=${GITHUB_REPOSITORY%%/*}
|
|
|
|
|
|
|
|
|
|
# Post the agent's activity trail (tool calls + reasoning) inline in the same comment so
|
|
|
|
@@ -248,7 +258,7 @@ br="$BRANCH"
|
|
|
|
|
ahead=$(git rev-list --count "origin/main..origin/$br" 2>/dev/null || echo 0)
|
|
|
|
|
if [ "${ahead:-0}" -eq 0 ]; then
|
|
|
|
|
# No changes on this branch — a plan / questions / analysis only.
|
|
|
|
|
post "$(printf '🤖 **@%s**\n\n%s%s' "$NAME" "$reply" "$activity")"
|
|
|
|
|
post "$(printf '%s%s' "$reply" "$activity")"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
@@ -266,7 +276,7 @@ if [ -z "$url" ]; then
|
|
|
|
|
url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null)
|
|
|
|
|
prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
|
|
|
|
|
fi
|
|
|
|
|
[ -z "$url" ] && { echo "PR open/lookup failed for $br — posting reply on issue instead"; post "$(printf '🤖 **@%s**\n\n%s%s' "$NAME" "$reply" "$activity")"; exit 0; }
|
|
|
|
|
[ -z "$url" ] && { echo "PR open/lookup failed for $br — posting reply on issue instead"; post "$(printf '%s%s' "$reply" "$activity")"; exit 0; }
|
|
|
|
|
|
|
|
|
|
# Posts to the PR thread when we have a PR number, else to the origin issue ($NUM).
|
|
|
|
|
prpost() {
|
|
|
|
@@ -274,11 +284,11 @@ prpost() {
|
|
|
|
|
[ -n "$n" ] && [ "$n" != "$NUM" ] && t="$n"
|
|
|
|
|
echo "posting to #$t"
|
|
|
|
|
curl -sS -w 'comment -> HTTP %{http_code}\n' -X POST "${hdr[@]}" \
|
|
|
|
|
"$API/issues/$t/comments" -d "$(jq -nc --arg b "$1" '{body:$b}')"
|
|
|
|
|
"$API/issues/$t/comments" -d "$(jq -nc --arg b "$1$MARK" '{body:$b}')"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if [ "$NEW" = "true" ]; then
|
|
|
|
|
prpost "$prnum" "$(printf '🤖 **@%s** — ✅ PR ready for review — @ffaerber please review & merge:\n- %s%s' "$NAME" "$url" "$activity")"
|
|
|
|
|
prpost "$prnum" "$(printf '✅ PR ready for review — @ffaerber please review & merge:\n- %s%s' "$url" "$activity")"
|
|
|
|
|
# AUTOPILOT: hand the fresh PR to @qa automatically (via TOK, so it fires a new run).
|
|
|
|
|
# @qa then verifies and — if green — merges + closes via its MERGE_PR marker. The comment lands
|
|
|
|
|
# on the PR thread ($prnum) so the next run resolves the origin issue's label from the branch
|
|
|
|
@@ -293,7 +303,7 @@ if [ "$NEW" = "true" ]; then
|
|
|
|
|
else
|
|
|
|
|
# Resume: just link the PR — its body and the diff already carry the description, so we don't
|
|
|
|
|
# repeat the full write-up in the comment (the reasoning trail below shows what this run did).
|
|
|
|
|
prpost "$prnum" "$(printf '🤖 **@%s** — pushed an update to the PR:\n- %s%s' "$NAME" "$url" "$activity")"
|
|
|
|
|
prpost "$prnum" "$(printf 'Pushed an update to the PR:\n- %s%s' "$url" "$activity")"
|
|
|
|
|
# AUTOPILOT: after a dev pushes a fix (e.g. following a @qa bounce), hand back to @qa to re-verify.
|
|
|
|
|
if [ "$AUTOPILOT" = "true" ] && [ -n "$TTOK" ] && [ -n "$prnum" ]; then
|
|
|
|
|
case "$NAME" in
|
|
|
|
|