@lead: externalize agent.yml inline scripts into .gitea/workflows/scripts/*.sh
Rebased onto the per-agent skill-scoping change so PR #25 carries both: - route.sh keeps the registry 'skills' allow-list and emits skills as a step output - install-opencode.sh writes the permission.skill block (deny-all + allow listed) Pure refactor otherwise: each step's shell moves to its own file, called via bash "$SCRIPTS/<name>.sh". The two extracted SKILL.md bodies are byte-identical to main; routing/config/publish behavior is unchanged. Because this is a reusable workflow (workflow_call) the runtime checkout is the caller's repo, so agent.yml now checks THIS repo out into .agents-workflow/ (pinned @main) and points $SCRIPTS there.
This commit is contained in:
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
# Publish — PR (dev agents) or comment (pm/qa), always reply in the issue.
|
||||
#
|
||||
# Required env (provided by the workflow step):
|
||||
# GT AGENT_TOKEN TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA
|
||||
# NAME MODE NUM TITLE BRANCH NEW GITHUB_SERVER_URL GITHUB_REPOSITORY
|
||||
set +e # publish is best-effort: a grep-no-match / curl non-zero must NOT kill the step
|
||||
# Post/PR as the agent's OWN Gitea user when its token is configured; else the built-in bot.
|
||||
case "$NAME" in
|
||||
pm) TOK="$TOKEN_PM";; senior) TOK="$TOKEN_SENIOR";; junior) TOK="$TOKEN_JUNIOR";;
|
||||
lead) TOK="$TOKEN_LEAD";; qa) TOK="$TOKEN_QA";; *) TOK="";;
|
||||
esac
|
||||
[ -z "$TOK" ] && TOK="$GT"
|
||||
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")
|
||||
post() { curl -sS -w 'comment -> HTTP %{http_code}\n' -X POST "${hdr[@]}" \
|
||||
"$API/issues/$NUM/comments" -d "$(jq -nc --arg b "$1" '{body:$b}')"; }
|
||||
|
||||
# drop machine-readable markers (DELEGATE / CLOSE_ISSUE / the BEGIN_SUBTASKS..END_SUBTASKS block)
|
||||
reply=$(awk '
|
||||
/^[[:space:]]*BEGIN_SUBTASKS/{s=1}
|
||||
/^[[:space:]]*DELEGATE:[[:space:]]*@/{next}
|
||||
/^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next}
|
||||
s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next }
|
||||
{print}
|
||||
' /tmp/agent_out.md 2>/dev/null)
|
||||
[ -z "$reply" ] && reply="_(Made changes without a text summary — see the diff below.)_"
|
||||
# Prefer the agent's clean delimited PR description; fall back to the whole reply.
|
||||
prdesc=$(awk '/BEGIN_PR_DESCRIPTION/{f=1;next} /END_PR_DESCRIPTION/{f=0} f' /tmp/agent_out.md)
|
||||
[ -z "$prdesc" ] && prdesc="$reply"
|
||||
|
||||
# comment-only roles (pm/qa): never change files
|
||||
if [ "$MODE" != "pr" ]; then
|
||||
git checkout -- . 2>/dev/null || true
|
||||
git clean -fd 2>/dev/null || true
|
||||
target=$(grep -oiE 'DELEGATE:[[:space:]]*@(junior|senior|lead|qa)' /tmp/agent_out.md 2>/dev/null | head -1 | grep -oiE '(junior|senior|lead|qa)' | tr '[:upper:]' '[:lower:]')
|
||||
# Visible comment: the reply text, or a sensible line if the agent only emitted a marker.
|
||||
msg="$reply"
|
||||
case "$msg" in ""|"_(Made changes"*) msg=$([ -n "$target" ] && echo "Handing off to @$target." || echo "_(no further comment)_") ;; esac
|
||||
post "$(printf '🤖 **@%s**\n\n%s' "$NAME" "$msg")"
|
||||
# Close the issue if the agent flagged it (maintainer said it's not needed / duplicate).
|
||||
if grep -qiE '^[[:space:]]*CLOSE_ISSUE[[:space:]]*$' /tmp/agent_out.md; then
|
||||
echo "closing issue #$NUM"
|
||||
curl -sS -X PATCH "${hdr[@]}" "$API/issues/$NUM" \
|
||||
-d '{"state":"closed"}' -w '\nclose -> HTTP %{http_code}\n' || true
|
||||
fi
|
||||
# BREAKDOWN: from a BEGIN_SUBTASKS block, create a milestone + one sub-issue per line
|
||||
# (linked to this issue). Sub-issues are NOT auto-started — maintainer mentions agents later.
|
||||
if grep -qiE '^[[:space:]]*BEGIN_SUBTASKS' /tmp/agent_out.md; then
|
||||
block=$(awk '/^[[:space:]]*BEGIN_SUBTASKS/{f=1;next} /^[[:space:]]*END_SUBTASKS/{f=0} f' /tmp/agent_out.md)
|
||||
ms=$(printf '%s\n' "$block" | sed -nE 's/^[[:space:]]*milestone:[[:space:]]*//Ip' | head -1)
|
||||
msid=""
|
||||
if [ -n "$ms" ]; then
|
||||
msid=$(curl -sS "${hdr[@]}" "$API/milestones?state=open&limit=100" | jq -r --arg t "$ms" 'if type=="array" then ([.[]|select(.title==$t)][0].id // empty) else empty end')
|
||||
[ -z "$msid" ] && msid=$(curl -sS -X POST "${hdr[@]}" "$API/milestones" -d "$(jq -nc --arg t "$ms" '{title:$t}')" | jq -r '.id // empty')
|
||||
echo "milestone '$ms' -> id ${msid:-?}"
|
||||
fi
|
||||
printf '%s\n' "$block" | grep -E '^[[:space:]]*-[[:space:]]' > /tmp/subtasks.txt || true
|
||||
links=""
|
||||
while IFS= read -r line; do
|
||||
item=$(printf '%s' "$line" | sed -E 's/^[[:space:]]*-[[:space:]]*//')
|
||||
title=${item%%::*}; body=${item#*::}; [ "$body" = "$item" ] && body=""
|
||||
title=$(printf '%s' "$title" | sed -E 's/[[:space:]]*$//')
|
||||
body=$(printf '%s' "$body" | sed -E 's/^[[:space:]]*//')
|
||||
[ -z "$title" ] && continue
|
||||
ibody=$(printf 'Part of #%s\n\n%s' "$NUM" "$body")
|
||||
if [ -n "$msid" ]; then
|
||||
payload=$(jq -nc --arg t "$title" --arg b "$ibody" --argjson m "$msid" '{title:$t,body:$b,milestone:$m}')
|
||||
else
|
||||
payload=$(jq -nc --arg t "$title" --arg b "$ibody" '{title:$t,body:$b}')
|
||||
fi
|
||||
n=$(curl -sS -X POST "${hdr[@]}" "$API/issues" -d "$payload" | jq -r '.number // empty')
|
||||
echo "created sub-issue #${n:-?}: $title"
|
||||
[ -n "$n" ] && links="$links\n- #$n — $title"
|
||||
done < /tmp/subtasks.txt
|
||||
post "$(printf '🤖 **@%s** — created sub-issues%s (mention an agent on each when ready):%b' "$NAME" "${ms:+ under milestone **$ms**}" "$links")"
|
||||
fi
|
||||
# Auto-delegate: if the plan names a teammate, trigger them via AGENT_TOKEN (a PAT, so it
|
||||
# fires a new workflow run — the built-in token cannot). Never targets @pm or self, so the
|
||||
# chain always terminates at a dev. The '🤖' guard on the trigger stops status-comment loops.
|
||||
if [ -n "$AGENT_TOKEN" ]; then
|
||||
# Only delegate on an explicit "DELEGATE: @<agent>" line — never on a prose mention,
|
||||
# so an agent that is asking the maintainer a question does not hand off prematurely.
|
||||
target=$(grep -oiE 'DELEGATE:[[:space:]]*@(junior|senior|lead|qa)' /tmp/agent_out.md 2>/dev/null \
|
||||
| head -1 | grep -oiE '(junior|senior|lead|qa)' | tr '[:upper:]' '[:lower:]')
|
||||
if [ -n "$target" ] && [ "$target" != "$NAME" ]; then
|
||||
echo "auto-delegating to @$target"
|
||||
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \
|
||||
"$API/issues/$NUM/comments" \
|
||||
-d "$(jq -nc --arg b "@$target please proceed with issue #$NUM per the plan above (delegated by $NAME)." '{body:$b}')" \
|
||||
-w '\ndelegate -> HTTP %{http_code}\n' || true
|
||||
else
|
||||
echo "no DELEGATE marker — not delegating (agent is asking or finished)"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The agent may have committed on the starting branch AND/OR created extra
|
||||
# ai/issue-N-<slug> branches. Commit any leftover on the current branch, push it, then
|
||||
# open a PR for EVERY ai/issue-N* branch that has commits beyond main.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add -A
|
||||
git commit -m "@$NAME: issue #$NUM"
|
||||
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")
|
||||
owner=${GITHUB_REPOSITORY%%/*}
|
||||
|
||||
# One PR per run: publish ONLY this run's own branch ($BRANCH), never sibling
|
||||
# ai/issue-N-* branches. This removes the multi-PR ambiguity that left the
|
||||
# activity log stranded on the triggering issue instead of the PR thread.
|
||||
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' "$NAME" "$reply")"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# NOTE: Gitea ignores the ?head= filter, so match the head branch client-side.
|
||||
resp=$(curl -sS "${hdr[@]}" "$API/pulls?state=open&limit=50" \
|
||||
| jq -r --arg br "$br" 'if type=="array" then (map(select(.head.ref==$br)) | .[0] // empty) else empty end' 2>/dev/null)
|
||||
url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null)
|
||||
prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
|
||||
if [ -z "$url" ]; then
|
||||
title="@$NAME: $TITLE"
|
||||
resp=$(curl -sS -X POST "${hdr[@]}" "$API/pulls" \
|
||||
-d "$(jq -nc --arg t "$title" --arg h "$br" --arg b "$prbody" \
|
||||
'{title:$t, head:$h, base:"main", body:$b}')")
|
||||
echo "PR create ($br): $resp"
|
||||
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' "$NAME" "$reply")"; exit 0; }
|
||||
|
||||
# Posts to the PR thread when we have a PR number, else to the origin issue ($NUM).
|
||||
prpost() {
|
||||
local n="$1"; shift; local t="$NUM"
|
||||
[ -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}')"
|
||||
}
|
||||
|
||||
if [ "$NEW" = "true" ]; then
|
||||
prpost "$prnum" "$(printf '🤖 **@%s** — ✅ PR ready for review — @ffaerber please review & merge:\n- %s' "$NAME" "$url")"
|
||||
else
|
||||
# Resume (comment is on a PR thread): include the write-up here too.
|
||||
prpost "$prnum" "$(printf '🤖 **@%s** — updated branch/PR:\n- %s\n\n%s' "$NAME" "$url" "$prdesc")"
|
||||
fi
|
||||
|
||||
# Post the agent's activity trail (tool calls + reasoning) as a separate comment so
|
||||
# it is visible on the PR thread. Additive — kept here even when nothing changed, so a
|
||||
# follow-up run (re-trigger) can see what this run did via the fetched issue thread.
|
||||
if [ -s /tmp/activity_log.md ]; then
|
||||
entries=$(wc -l < /tmp/activity_log.md 2>/dev/null || echo 0)
|
||||
log=$(cat /tmp/activity_log.md)
|
||||
prpost "$prnum" "$(printf '🤖 **@%s** — activity log (%s entries):\n<details>\n<summary>tool calls & reasoning</summary>\n\n%s\n\n</details>' "$NAME" "$entries" "$log")"
|
||||
fi
|
||||
Reference in New Issue
Block a user