98 lines
5.8 KiB
Bash
Executable File
98 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Route agent + prepare branch.
|
|
# Reads the event context from env (set by the calling step), writes the agent registry to
|
|
# /tmp/agents.json, picks which agent to run, emits step outputs (name/model/vision/mode/branch/new)
|
|
# to $GITHUB_OUTPUT, configures git identity, and prepares/publishes the working branch.
|
|
#
|
|
# Required env (all provided by the workflow step): BODY IBODY CID IS_PR NUM GT
|
|
# TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA
|
|
# GITHUB_SERVER_URL GITHUB_REPOSITORY GITHUB_OUTPUT
|
|
set -eu
|
|
|
|
# --- agent registry: model + capabilities + mode + role + skills ---
|
|
# The registry is the SINGLE SOURCE OF TRUTH, kept in agents.json next to this
|
|
# script. install-opencode.sh derives its ollama-cloud provider `models:` map
|
|
# from the same file, so an agent's model can never be missing from the provider
|
|
# config — drift is impossible by construction. See issue #31.
|
|
# `skills` is the allow-list of opencode Skills each agent may load. It scopes the
|
|
# `permission.skill` block written into opencode.json (see install-opencode.sh) so an agent only
|
|
# ever sees (and can load) the skills relevant to its role. Skills NOT listed here are hidden from
|
|
# that agent entirely — not even the one-line summary appears in its <available_skills>, so the
|
|
# full API/how-to detail never reaches an agent that shouldn't act on it. A teammate can still learn
|
|
# *that* another agent has a capability from the roster and ask them to use it.
|
|
AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json"
|
|
cp "$AGENTS_JSON" /tmp/agents.json
|
|
# On a new issue, @pm auto-assesses. On a comment, route by the @mention.
|
|
# A comment event has a comment id (CID); an issue-opened event does not. (event_name is unreliable
|
|
# here — see agent.yml: this reusable workflow sees it as 'workflow_call'.)
|
|
if [ -n "$CID" ]; then scan="$BODY"; else scan="$IBODY"; fi
|
|
name=""
|
|
# FIRST MATCH IN THIS LIST ORDER WINS when a comment mentions several agents. The order is
|
|
# load-bearing for the flow's trigger comments: "@pm — @qa approved …" must route to @pm (pm is
|
|
# checked first), while "@junior please address @qa's review …" must route to the dev (devs are
|
|
# checked before qa). If you add an agent or reword a trigger in publish.sh, re-check this order.
|
|
for a in pm junior senior lead qa ops intern; do
|
|
case "$scan" in *"@$a"*) name=$a; break;; esac
|
|
done
|
|
if [ -z "$name" ]; then
|
|
if [ -z "$CID" ]; then name=pm; else echo "no known agent mentioned"; exit 1; fi
|
|
fi
|
|
model=$(jq -r --arg a "$name" '.[$a].model' /tmp/agents.json)
|
|
vision=$(jq -r --arg a "$name" '.[$a].vision' /tmp/agents.json)
|
|
mode=$(jq -r --arg a "$name" '.[$a].mode' /tmp/agents.json)
|
|
# Compact JSON array of the skills this agent may load (scopes permission.skill in install-opencode.sh).
|
|
skills=$(jq -c --arg a "$name" '.[$a].skills // []' /tmp/agents.json)
|
|
echo "Routing to @$name (model=$model vision=$vision mode=$mode skills=$skills)"
|
|
{ echo "name=$name"; echo "model=$model"; echo "vision=$vision"; echo "mode=$mode"; echo "skills=$skills"; } >> "$GITHUB_OUTPUT"
|
|
|
|
# Act as the agent's own Gitea user when its token is set; 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";; ops) TOK="$TOKEN_OPS";; intern) TOK="$TOKEN_INTERN";; *) 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")
|
|
branch_ref=""
|
|
if [ -n "$IS_PR" ]; then # comment on a PR -> resume its branch
|
|
ref=$(curl -s -H "Authorization: token $GT" "$API/pulls/$NUM" | jq -r .head.ref)
|
|
branch_ref="$ref"
|
|
git fetch origin "$ref" && git checkout "$ref"
|
|
{ echo "branch=$ref"; echo "new=false"; } >> "$GITHUB_OUTPUT"
|
|
elif git ls-remote --exit-code --heads origin "ai/issue-$NUM" >/dev/null 2>&1; then
|
|
# comment on an issue whose branch ALREADY exists (a prior run / open PR) -> RESUME it, so new
|
|
# commits fast-forward onto the same branch and update its PR. Branching fresh from main here would
|
|
# be rejected on push as non-fast-forward and the new work would be silently lost (see issue #17).
|
|
git fetch origin "ai/issue-$NUM" && git checkout "ai/issue-$NUM"
|
|
{ echo "branch=ai/issue-$NUM"; echo "new=false"; } >> "$GITHUB_OUTPUT"
|
|
else # comment on an issue, no branch yet -> new branch
|
|
git checkout -b "ai/issue-$NUM"
|
|
{ echo "branch=ai/issue-$NUM"; echo "new=true"; } >> "$GITHUB_OUTPUT"
|
|
# For dev agents, publish the branch immediately and tell the maintainer where to watch.
|
|
if [ "$mode" = "pr" ]; then
|
|
git push -u origin "HEAD:ai/issue-$NUM" || true
|
|
url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/ai/issue-$NUM"
|
|
curl -sS -X POST "${hdr[@]}" "$API/issues/$NUM/comments" \
|
|
-d "$(jq -nc --arg b "🔨 Building on branch [\`ai/issue-$NUM\`]($url) — I'll open a PR when it's ready.
|
|
|
|
<!-- 🤖 agent reply — do not trigger -->" '{body:$b}')" >/dev/null || true
|
|
fi
|
|
fi
|
|
|
|
# --- Autopilot gate: read the `autopilot` label FRESH every run. ---
|
|
# Presence of this label is the opt-in switch (and the kill switch: remove it mid-flight and the
|
|
# next run reverts to normal human-approval behavior). When @qa is triggered on a PR thread, the
|
|
# label lives on the ORIGIN issue (ai/issue-N), so resolve N from the branch name.
|
|
issnum="$NUM"
|
|
case "$IS_PR" in ?*) issnum=$(printf '%s' "$branch_ref" | sed -nE 's,^ai/issue-([0-9]+).*,\1,p');; esac
|
|
[ -z "$issnum" ] && issnum="$NUM"
|
|
autopilot=false
|
|
if curl -sS -H "Authorization: token $GT" "$API/issues/$issnum/labels" 2>/dev/null \
|
|
| jq -e 'any(.[]?; .name=="autopilot")' >/dev/null 2>&1; then
|
|
autopilot=true
|
|
fi
|
|
echo "autopilot (autopilot label on #$issnum)=$autopilot"
|
|
{ echo "autopilot=$autopilot"; echo "issnum=$issnum"; } >> "$GITHUB_OUTPUT"
|