This commit is contained in:
@@ -107,6 +107,7 @@ jobs:
|
||||
TOKEN_LEAD: ${{ secrets.TOKEN_LEAD }}
|
||||
TOKEN_QA: ${{ secrets.TOKEN_QA }}
|
||||
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
|
||||
TOKEN_INTERN: ${{ secrets.TOKEN_INTERN }}
|
||||
run: bash "$SCRIPTS/route.sh"
|
||||
|
||||
- name: Install opencode + provider config (+ Playwright MCP for browser agents)
|
||||
@@ -176,7 +177,7 @@ jobs:
|
||||
# Powers the gitea-api / gitea-admin skills — each agent calls Gitea as itself. Every
|
||||
# consuming repo now carries the per-agent TOKEN_* secrets (org-level for gitea/*, user-level
|
||||
# for ffaerber/*), so there is no shared-token fallback.
|
||||
SELF_TOKEN: ${{ steps.prep.outputs.name == 'pm' && secrets.TOKEN_PM || steps.prep.outputs.name == 'junior' && secrets.TOKEN_JUNIOR || steps.prep.outputs.name == 'senior' && secrets.TOKEN_SENIOR || steps.prep.outputs.name == 'lead' && secrets.TOKEN_LEAD || steps.prep.outputs.name == 'qa' && secrets.TOKEN_QA || steps.prep.outputs.name == 'ops' && secrets.TOKEN_OPS || '' }}
|
||||
SELF_TOKEN: ${{ steps.prep.outputs.name == 'pm' && secrets.TOKEN_PM || steps.prep.outputs.name == 'junior' && secrets.TOKEN_JUNIOR || steps.prep.outputs.name == 'senior' && secrets.TOKEN_SENIOR || steps.prep.outputs.name == 'lead' && secrets.TOKEN_LEAD || steps.prep.outputs.name == 'qa' && secrets.TOKEN_QA || steps.prep.outputs.name == 'ops' && secrets.TOKEN_OPS || steps.prep.outputs.name == 'intern' && secrets.TOKEN_INTERN || '' }}
|
||||
NAME: ${{ steps.prep.outputs.name }}
|
||||
MODEL: ${{ steps.prep.outputs.model }}
|
||||
VISION: ${{ steps.prep.outputs.vision }}
|
||||
@@ -209,6 +210,7 @@ jobs:
|
||||
TOKEN_LEAD: ${{ secrets.TOKEN_LEAD }}
|
||||
TOKEN_QA: ${{ secrets.TOKEN_QA }}
|
||||
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
|
||||
TOKEN_INTERN: ${{ secrets.TOKEN_INTERN }}
|
||||
NAME: ${{ steps.prep.outputs.name }}
|
||||
MODE: ${{ steps.prep.outputs.mode }}
|
||||
NUM: ${{ github.event.issue.number }}
|
||||
@@ -235,6 +237,7 @@ jobs:
|
||||
TOKEN_LEAD: ${{ secrets.TOKEN_LEAD }}
|
||||
TOKEN_QA: ${{ secrets.TOKEN_QA }}
|
||||
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
|
||||
TOKEN_INTERN: ${{ secrets.TOKEN_INTERN }}
|
||||
NAME: ${{ steps.prep.outputs.name }}
|
||||
MODE: ${{ steps.prep.outputs.mode }}
|
||||
NUM: ${{ github.event.issue.number }}
|
||||
|
||||
@@ -50,5 +50,12 @@
|
||||
"gitea-admin"
|
||||
],
|
||||
"desc": "Gitea operator — administers the Gitea instance itself: create orgs/users/repos, manage labels and secrets, mint scoped per-user tokens, bootstrap new repos with the agent caller. Comments only; never edits code. ALWAYS confirms before any destructive action (delete user/repo/org)."
|
||||
},
|
||||
"intern": {
|
||||
"model": "ollama/ornith:35b",
|
||||
"vision": false,
|
||||
"mode": "pr",
|
||||
"skills": [],
|
||||
"desc": "Intern — very basic tasks only, routed to the local Ollama model (ornith:35b). Text-only, cannot read images. Escalates anything non-trivial to @junior, @senior or @lead."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,17 @@ SKILLS="${SKILLS:-[]}"
|
||||
PERM=$(jq -nc --argjson s "$SKILLS" '
|
||||
{skill: ( {"*":"deny"} + (reduce $s[] as $k ({}; . + {($k):"allow"})) )}')
|
||||
# Two ollama providers: local self-hosted (ornith) + Ollama Cloud (gemma4/kimi-k2.7-code/glm-5.2/minimax-m3).
|
||||
# The ollama-cloud `models:` map is DERIVED from agents.json (the single source of truth, shared with
|
||||
# route.sh) so every model an agent is routed to is always declared in the provider config. Only the
|
||||
# `ollama-cloud/` provider prefix models participate — e.g. `anthropic/claude-opus-4-8` (@lead) is a
|
||||
# built-in provider and `ornith:35b` is local-only, neither belongs here. See issue #31.
|
||||
# The provider `models:` maps are DERIVED from agents.json (the single source of truth, shared with
|
||||
# route.sh) so every model an agent is routed to is always declared in the provider config.
|
||||
# `ollama-cloud/` prefix models go to the cloud provider; `ollama/` prefix models go to the local
|
||||
# provider. Built-in providers (e.g. `anthropic/claude-opus-4-8` for @lead) are not derived here.
|
||||
# See issue #31.
|
||||
AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json"
|
||||
CLOUD_MODELS=$(jq -r '[.[] | .model | select(startswith("ollama-cloud/")) | sub("^ollama-cloud/";"")] | map({(.):{}}) | add // {}' "$AGENTS_JSON")
|
||||
jq -n --argjson mcp "$MCP" --argjson perm "$PERM" --argjson cloud "$CLOUD_MODELS" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" '{
|
||||
LOCAL_MODELS=$(jq -r '[.[] | .model | select(startswith("ollama/")) | sub("^ollama/";"")] | map({(.):{}}) | add // {"ornith:35b":{}}' "$AGENTS_JSON")
|
||||
jq -n --argjson mcp "$MCP" --argjson perm "$PERM" --argjson cloud "$CLOUD_MODELS" --argjson local "$LOCAL_MODELS" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" '{
|
||||
provider: {
|
||||
ollama: {npm:"@ai-sdk/openai-compatible", options:{baseURL:($url+"/v1")}, models:{"ornith:35b":{}}},
|
||||
ollama: {npm:"@ai-sdk/openai-compatible", options:{baseURL:($url+"/v1")}, models:$local},
|
||||
"ollama-cloud": {npm:"@ai-sdk/openai-compatible", options:{baseURL:"https://ollama.com/v1", apiKey:$ckey}, models:$cloud}
|
||||
},
|
||||
permission: $perm,
|
||||
|
||||
@@ -9,7 +9,7 @@ set +e # publish is best-effort: a grep-no-match / curl non-zero must NOT kill
|
||||
# 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";; ops) TOK="$TOKEN_OPS";; *) TOK="";;
|
||||
lead) TOK="$TOKEN_LEAD";; qa) TOK="$TOKEN_QA";; ops) TOK="$TOKEN_OPS";; intern) TOK="$TOKEN_INTERN";; *) TOK="";;
|
||||
esac
|
||||
[ -z "$TOK" ] && TOK="$GT"
|
||||
# Trigger token: comments that must FIRE the next workflow (delegation, autopilot) and PR merges
|
||||
@@ -108,8 +108,8 @@ if [ "$MODE" != "pr" ]; then
|
||||
if grep -qiE '^[[:space:]]*APPROVE[[:space:]]*$' /tmp/agent_out.md; then
|
||||
post_to "$ISSN" "$(printf '✅ Reviewed PR #%s — looks good.\n\n%s%s' "${PRN:-?}" "$reply" "$activity")"
|
||||
trig "$ISSN" "@pm — I have reviewed and approved PR #${PRN:-?} (issue #$ISSN). Over to you."
|
||||
elif grep -qiE '^[[:space:]]*BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md; then
|
||||
dev=$(grep -oiE 'BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md | head -1 | grep -oiE '(junior|senior|lead)' | tr '[:upper:]' '[:lower:]')
|
||||
elif grep -qiE '^[[:space:]]*BOUNCE:[[:space:]]*@(junior|senior|lead|intern)' /tmp/agent_out.md; then
|
||||
dev=$(grep -oiE 'BOUNCE:[[:space:]]*@(junior|senior|lead|intern)' /tmp/agent_out.md | head -1 | grep -oiE '(junior|senior|lead|intern)' | tr '[:upper:]' '[:lower:]')
|
||||
[ -z "$dev" ] && [ -n "$PRN" ] && dev=$(curl -sS "${hdr[@]}" "$API/pulls/$PRN" | jq -r '.user.login // "junior"')
|
||||
dest="${PRN:-$NUM}"
|
||||
post_to "$dest" "$reply$activity" # recommendations, on the PR
|
||||
@@ -121,6 +121,7 @@ if [ "$MODE" != "pr" ]; then
|
||||
else
|
||||
n=$((prior + 1))
|
||||
trig "$dest" "@${dev:-junior} please address my review above and update PR #${PRN:-?} (fix attempt $n/3)."
|
||||
|
||||
fi
|
||||
elif grep -qiE '^[[:space:]]*HALT([_ ]AUTOPILOT)?[[:space:]]*$' /tmp/agent_out.md; then
|
||||
[ "$AUTOPILOT" = "true" ] && del_autopilot_label "$ISSN"
|
||||
@@ -132,7 +133,7 @@ if [ "$MODE" != "pr" ]; then
|
||||
fi
|
||||
|
||||
# ---------- @pm / @ops: issue-thread orchestration ----------
|
||||
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:]')
|
||||
target=$(grep -oiE 'DELEGATE:[[:space:]]*@(junior|senior|lead|qa|intern)' /tmp/agent_out.md 2>/dev/null | head -1 | grep -oiE '(junior|senior|lead|qa|intern)' | 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
|
||||
@@ -297,6 +298,6 @@ else
|
||||
# on the PR thread. The qa↔dev loop is direct — it does NOT go back through @pm each round.
|
||||
prpost "$prnum" "$(printf 'Pushed an update to PR #%s.%s' "$prnum" "$activity")"
|
||||
case "$NAME" in
|
||||
junior|senior|lead) trig "$prnum" "@qa please re-verify PR #$prnum — I have pushed an update." ;;
|
||||
junior|senior|lead|intern) trig "$prnum" "@qa please re-verify PR #$prnum — I have pushed an update." ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -17,7 +17,7 @@ set +e
|
||||
# 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";; ops) TOK="$TOKEN_OPS";; *) TOK="";;
|
||||
lead) TOK="$TOKEN_LEAD";; qa) TOK="$TOKEN_QA";; ops) TOK="$TOKEN_OPS";; intern) TOK="$TOKEN_INTERN";; *) TOK="";;
|
||||
esac
|
||||
[ -z "$TOK" ] && TOK="$GT"
|
||||
# Trigger token: the @pm hand-back below must FIRE a new run, which the built-in token cannot.
|
||||
|
||||
@@ -31,7 +31,7 @@ name=""
|
||||
# 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; do
|
||||
for a in pm junior senior lead qa ops intern; do
|
||||
case "$scan" in *"@$a"*) name=$a; break;; esac
|
||||
done
|
||||
if [ -z "$name" ]; then
|
||||
@@ -48,7 +48,7 @@ echo "Routing to @$name (model=$model vision=$vision mode=$mode skills=$skills)"
|
||||
# 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";; *) TOK="";;
|
||||
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"
|
||||
|
||||
@@ -26,7 +26,7 @@ if [ "$MODE" = "comment" ]; then
|
||||
ACTION="You do NOT edit files, create branches, or write a PR description. Respond with your analysis,
|
||||
plan, research, or clarifying questions — your reply becomes a comment on the issue.
|
||||
To hand work to a teammate, end your reply with EXACTLY one line: 'DELEGATE: @<agent>' (one of
|
||||
@junior @senior @lead @qa) — but ONLY when you are ready to hand off AND need nothing further from the
|
||||
@junior @senior @lead @qa @intern) — but ONLY when you are ready to hand off AND need nothing further from the
|
||||
maintainer. If you are asking @ffaerber to confirm or decide ANYTHING, do NOT include a DELEGATE line;
|
||||
just ask and wait. Never ask for confirmation and delegate in the same reply. Mentioning a teammate in
|
||||
prose does NOT delegate — only the DELEGATE line does.
|
||||
@@ -86,9 +86,9 @@ if [ "$MODE" = "comment" ]; then
|
||||
issue and hands back to @pm (who tells the creator, or in autopilot merges). You do NOT merge.
|
||||
- 'BOUNCE: @<dev>' — something needs changing. FIRST spell out, specifically and actionably, exactly
|
||||
what to change (file, label, value, hostname, …), THEN end with the BOUNCE line naming who fixes
|
||||
it (@junior / @senior / @lead — usually whoever built it). The automation sends the PR back and
|
||||
re-verifies with you. After 3 rounds it stops and hands to @ffaerber — so list ALL problems at
|
||||
once, not one at a time.
|
||||
it (@junior / @senior / @lead / @intern — usually whoever built it). The automation sends the PR back and
|
||||
re-verifies with you. After 3 rounds it stops and hands to @ffaerber — so list ALL problems at
|
||||
once, not one at a time.
|
||||
- 'HALT' — the problem is NOT something a dev can fix (the request is ambiguous / needs a human
|
||||
decision). Hands back to @ffaerber.
|
||||
Emit AT MOST one marker, and only after you have actually verified."
|
||||
|
||||
@@ -13,6 +13,7 @@ Shared **AI dev-team** workflow for Gitea Actions, reusable across repos. It giv
|
||||
| `@lead` | `anthropic/claude-opus-4-8` | yes | pr | `gitea-api` | Tech lead — the hardest problems, architecture, and final calls. |
|
||||
| `@qa` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | QA / reviewer — reads the PR diff, drives a headless browser (Playwright) to verify behavior; recommendations on the PR, pass/fail verdict on the issue. Never edits code, never merges. |
|
||||
| `@ops` | `anthropic/claude-opus-4-8` | no | comment | `gitea-admin` | Gitea operator — administers the instance itself (create orgs/users/repos, labels, secrets, scoped per-user tokens, bootstrap repos). Comments only; never edits code. Confirms before destructive actions. |
|
||||
| `@intern` | `ollama/ornith:35b` | no | pr | — | Intern — very basic tasks only, routed to the local Ollama model (`ornith:35b`). Text-only, cannot read images. Escalates anything non-trivial to `@junior`, `@senior` or `@lead`. |
|
||||
|
||||
The registry `.gitea/workflows/scripts/agents.json` is the source of truth for this mapping — if you
|
||||
change a model or an agent's skills there, update this table too. (Repo-specific skills, e.g. a
|
||||
|
||||
Reference in New Issue
Block a user