Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbe3e3cbc0 | ||
|
|
85be0a3288 | ||
|
|
e29b8141f8 |
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
name: agent
|
name: agent
|
||||||
# Reusable AI-agent workflow, shared across repos. A caller repo triggers on issue_comment/issues
|
# Reusable AI-agent workflow, shared across repos. A caller repo triggers on issue_comment/issues
|
||||||
# and invokes this via: uses: gitea/agents/.gitea/workflows/agent.yml@main (secrets: inherit).
|
# and invokes this via: uses: gitea/agents/.gitea/workflows/agent.yml@main (explicit secret map,
|
||||||
|
# see .gitea/workflows/ai-agent.yml for the stub every caller copies).
|
||||||
# The gate + steps run in the caller's event context (github.event.* / github.repository are the caller's).
|
# The gate + steps run in the caller's event context (github.event.* / github.repository are the caller's).
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
@@ -29,6 +30,17 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
TOKEN_INTERN:
|
TOKEN_INTERN:
|
||||||
required: false
|
required: false
|
||||||
|
# Secrets for caller-provided skills (.gitea/agent-skills/<name>/setup.sh). Their names are
|
||||||
|
# known only to the caller repo, but SECRETS_JSON below is toJSON(secrets) and can therefore
|
||||||
|
# only ever contain what this contract declares — so every such secret has to be listed here,
|
||||||
|
# optional, or the skill silently no-ops and the agent loses the capability without an error.
|
||||||
|
# These three are homelab's node1-ssh skill; add further caller-skill secrets alongside them.
|
||||||
|
SWARM_HOST:
|
||||||
|
required: false
|
||||||
|
SWARM_USER:
|
||||||
|
required: false
|
||||||
|
SSH_PRIV_KEY_B64:
|
||||||
|
required: false
|
||||||
|
|
||||||
# Pinned opencode version — used to install it and to key the CI cache below.
|
# Pinned opencode version — used to install it and to key the CI cache below.
|
||||||
env:
|
env:
|
||||||
@@ -256,6 +268,7 @@ jobs:
|
|||||||
HAS_IMAGES: ${{ steps.imgs.outputs.has_images }}
|
HAS_IMAGES: ${{ steps.imgs.outputs.has_images }}
|
||||||
BRANCH: ${{ steps.prep.outputs.branch }}
|
BRANCH: ${{ steps.prep.outputs.branch }}
|
||||||
AUTOPILOT: ${{ steps.prep.outputs.autopilot }} # 'true' when the issue carries the `autopilot` label
|
AUTOPILOT: ${{ steps.prep.outputs.autopilot }} # 'true' when the issue carries the `autopilot` label
|
||||||
|
AUTOMERGE: ${{ steps.prep.outputs.automerge }} # 'false' when `human-merge` is also set
|
||||||
NUM: ${{ github.event.issue.number }}
|
NUM: ${{ github.event.issue.number }}
|
||||||
TITLE: ${{ github.event.issue.title }}
|
TITLE: ${{ github.event.issue.title }}
|
||||||
IBODY: ${{ github.event.issue.body }}
|
IBODY: ${{ github.event.issue.body }}
|
||||||
@@ -293,6 +306,7 @@ jobs:
|
|||||||
NEW: ${{ steps.prep.outputs.new }}
|
NEW: ${{ steps.prep.outputs.new }}
|
||||||
IS_PR: ${{ github.event.issue.pull_request }} # set when this run is on a PR thread
|
IS_PR: ${{ github.event.issue.pull_request }} # set when this run is on a PR thread
|
||||||
AUTOPILOT: ${{ steps.prep.outputs.autopilot }} # 'true' when the origin issue carries `autopilot`
|
AUTOPILOT: ${{ steps.prep.outputs.autopilot }} # 'true' when the origin issue carries `autopilot`
|
||||||
|
AUTOMERGE: ${{ steps.prep.outputs.automerge }} # 'false' when `human-merge` is also set
|
||||||
ISSNUM: ${{ steps.prep.outputs.issnum }} # origin issue number (resolved from branch on PR threads)
|
ISSNUM: ${{ steps.prep.outputs.issnum }} # origin issue number (resolved from branch on PR threads)
|
||||||
run: bash "$SCRIPTS/publish.sh"
|
run: bash "$SCRIPTS/publish.sh"
|
||||||
|
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ jobs:
|
|||||||
TOKEN_QA: ${{ secrets.TOKEN_QA }}
|
TOKEN_QA: ${{ secrets.TOKEN_QA }}
|
||||||
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
|
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
|
||||||
TOKEN_INTERN: ${{ secrets.TOKEN_INTERN }}
|
TOKEN_INTERN: ${{ secrets.TOKEN_INTERN }}
|
||||||
|
# Secrets for this repo's own caller-provided skills (.gitea/agent-skills/). Unset in a repo
|
||||||
|
# that ships no such skill, which is harmless: the skill's setup.sh no-ops on an empty value.
|
||||||
|
SWARM_HOST: ${{ secrets.SWARM_HOST }}
|
||||||
|
SWARM_USER: ${{ secrets.SWARM_USER }}
|
||||||
|
SSH_PRIV_KEY_B64: ${{ secrets.SSH_PRIV_KEY_B64 }}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
# Required env (provided by the workflow step):
|
# Required env (provided by the workflow step):
|
||||||
# GT TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA
|
# GT TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA
|
||||||
# NAME MODE NUM TITLE BRANCH NEW GITHUB_SERVER_URL GITHUB_REPOSITORY
|
# NAME MODE NUM TITLE BRANCH NEW GITHUB_SERVER_URL GITHUB_REPOSITORY
|
||||||
# IS_PR AUTOPILOT ISSNUM (autopilot: @qa label-gated merge/halt + auto-trigger @qa on a fresh PR)
|
# IS_PR AUTOPILOT AUTOMERGE ISSNUM (autopilot: @qa label-gated merge/halt + auto-trigger @qa on a fresh PR)
|
||||||
set +e # publish is best-effort: a grep-no-match / curl non-zero must NOT kill the step
|
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.
|
# Post/PR as the agent's OWN Gitea user when its token is configured; else the built-in bot.
|
||||||
case "$NAME" in
|
case "$NAME" in
|
||||||
@@ -222,9 +222,10 @@ if [ "$MODE" != "pr" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# --- @pm autopilot merge: @pm is the ONLY agent that merges, and ONLY under the autopilot label ---
|
# --- @pm autopilot merge: @pm is the ONLY agent that merges, and ONLY under the autopilot label ---
|
||||||
|
# (and never when `human-merge` is also set — AUTOMERGE is false then).
|
||||||
# (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in
|
# (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in
|
||||||
# token, so the push to main fires the deploy. TOKEN_PM must carry write:repository.
|
# token, so the push to main fires the deploy. TOKEN_PM must carry write:repository.
|
||||||
if [ "$NAME" = "pm" ] && [ "$AUTOPILOT" = "true" ] && grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
if [ "$NAME" = "pm" ] && [ "$AUTOMERGE" = "true" ] && grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
||||||
PRS=$(resolve_prs); PRN=${PRS##* }; CNT=$(echo "$PRS" | wc -w)
|
PRS=$(resolve_prs); PRN=${PRS##* }; CNT=$(echo "$PRS" | wc -w)
|
||||||
if [ -z "$PRN" ]; then
|
if [ -z "$PRN" ]; then
|
||||||
echo "MERGE_PR but no open PR found for issue #$ISSN"
|
echo "MERGE_PR but no open PR found for issue #$ISSN"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ if [ -z "$name" ]; then
|
|||||||
# mode=skip so every later step no-ops — a red run for a non-agent comment is just noise.
|
# mode=skip so every later step no-ops — a red run for a non-agent comment is just noise.
|
||||||
echo "no known agent mentioned (word-boundary) — skipping run"
|
echo "no known agent mentioned (word-boundary) — skipping run"
|
||||||
{ echo "name=none"; echo "model=none"; echo "fallback="; echo "vision=false"; echo "mode=skip"; echo "skills=[]";
|
{ echo "name=none"; echo "model=none"; echo "fallback="; echo "vision=false"; echo "mode=skip"; echo "skills=[]";
|
||||||
echo "branch=main"; echo "new=false"; echo "autopilot=false"; echo "issnum=$NUM"; } >> "$GITHUB_OUTPUT"
|
echo "branch=main"; echo "new=false"; echo "autopilot=false"; echo "automerge=false"; echo "issnum=$NUM"; } >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -121,10 +121,12 @@ fi
|
|||||||
issnum="$NUM"
|
issnum="$NUM"
|
||||||
case "$IS_PR" in ?*) issnum=$(printf '%s' "$branch_ref" | sed -nE 's,^ai/issue-([0-9]+).*,\1,p');; esac
|
case "$IS_PR" in ?*) issnum=$(printf '%s' "$branch_ref" | sed -nE 's,^ai/issue-([0-9]+).*,\1,p');; esac
|
||||||
[ -z "$issnum" ] && issnum="$NUM"
|
[ -z "$issnum" ] && issnum="$NUM"
|
||||||
autopilot=false
|
# `human-merge` alongside it keeps the hands-off build but leaves the merge to a human.
|
||||||
if curl -sS -H "Authorization: token $GT" "$API/issues/$issnum/labels" 2>/dev/null \
|
labels=$(curl -sS -H "Authorization: token $GT" "$API/issues/$issnum/labels" 2>/dev/null)
|
||||||
| jq -e 'any(.[]?; .name=="autopilot")' >/dev/null 2>&1; then
|
autopilot=false; automerge=false
|
||||||
|
if printf '%s' "$labels" | jq -e 'any(.[]?; .name=="autopilot")' >/dev/null 2>&1; then
|
||||||
autopilot=true
|
autopilot=true
|
||||||
|
printf '%s' "$labels" | jq -e 'any(.[]?; .name=="human-merge")' >/dev/null 2>&1 || automerge=true
|
||||||
fi
|
fi
|
||||||
echo "autopilot (autopilot label on #$issnum)=$autopilot"
|
echo "autopilot (autopilot label on #$issnum)=$autopilot automerge=$automerge"
|
||||||
{ echo "autopilot=$autopilot"; echo "issnum=$issnum"; } >> "$GITHUB_OUTPUT"
|
{ echo "autopilot=$autopilot"; echo "automerge=$automerge"; echo "issnum=$issnum"; } >> "$GITHUB_OUTPUT"
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
# plain-text reply (/tmp/agent_out.md) plus the raw event stream (/tmp/events.jsonl).
|
# plain-text reply (/tmp/agent_out.md) plus the raw event stream (/tmp/events.jsonl).
|
||||||
#
|
#
|
||||||
# Required env (provided by the workflow step):
|
# Required env (provided by the workflow step):
|
||||||
# XAI_API_KEY SELF_TOKEN NAME MODEL VISION MODE HAS_IMAGES BRANCH AUTOPILOT NUM TITLE
|
# XAI_API_KEY SELF_TOKEN NAME MODEL VISION MODE HAS_IMAGES BRANCH AUTOPILOT AUTOMERGE NUM TITLE
|
||||||
# IBODY CMT
|
# IBODY CMT
|
||||||
# FILES (the opencode -f image flags, from the imgs step output)
|
# FILES (the opencode -f image flags, from the imgs step output)
|
||||||
# AUTOPILOT is 'true' when the issue carries the `autopilot` label (label-gated autopilot mode).
|
# AUTOPILOT is 'true' when the issue carries the `autopilot` label (label-gated autopilot mode).
|
||||||
@@ -74,14 +74,21 @@ if [ "$MODE" = "comment" ]; then
|
|||||||
auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready."
|
auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready."
|
||||||
if [ "$AUTOPILOT" = "true" ]; then
|
if [ "$AUTOPILOT" = "true" ]; then
|
||||||
ACTION="$ACTION
|
ACTION="$ACTION
|
||||||
AUTOPILOT MODE IS ACTIVE (this issue carries the 'autopilot' label) — it changes exactly TWO
|
AUTOPILOT MODE IS ACTIVE (this issue carries the 'autopilot' label) — it changes only the
|
||||||
things for you; everything else above is unchanged:
|
phases below; everything else above is unchanged:
|
||||||
- PHASE 1: do NOT ask '@ffaerber ready to build?'. Present your SHORT plan AND end with a
|
- PHASE 1: do NOT ask '@ffaerber ready to build?'. Present your SHORT plan AND end with a
|
||||||
'DELEGATE: @<dev>' line in the SAME reply. Only skip delegating (and ask @ffaerber) if the task
|
'DELEGATE: @<dev>' line in the SAME reply. Only skip delegating (and ask @ffaerber) if the task
|
||||||
is genuinely ambiguous or unsafe.
|
is genuinely ambiguous or unsafe."
|
||||||
|
if [ "$AUTOMERGE" = "true" ]; then
|
||||||
|
ACTION="$ACTION
|
||||||
- PHASE 3: do NOT ask the human to merge. When @qa has approved, end your reply with EXACTLY
|
- PHASE 3: do NOT ask the human to merge. When @qa has approved, end your reply with EXACTLY
|
||||||
'MERGE_PR' — the automation merges the PR and closes this issue. You are the ONLY agent that
|
'MERGE_PR' — the automation merges the PR and closes this issue. You are the ONLY agent that
|
||||||
merges, and only here."
|
merges, and only here."
|
||||||
|
else
|
||||||
|
ACTION="$ACTION
|
||||||
|
- PHASE 3 is UNCHANGED: this issue also carries 'human-merge', so when @qa has approved, tell
|
||||||
|
@ffaerber the PR is ready to merge. Do NOT emit 'MERGE_PR'."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ "$NAME" = "qa" ]; then
|
if [ "$NAME" = "qa" ]; then
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ An issue labeled **`autopilot`** runs without the usual human checkpoints:
|
|||||||
- **Kill switch:** remove the `autopilot` label at any time. The label is re-read fresh at the
|
- **Kill switch:** remove the `autopilot` label at any time. The label is re-read fresh at the
|
||||||
start of every run, so the next agent turn reverts to normal human-approval behavior. `@qa` also
|
start of every run, so the next agent turn reverts to normal human-approval behavior. `@qa` also
|
||||||
removes the label itself whenever it halts on a bug or a failed merge.
|
removes the label itself whenever it halts on a bug or a failed merge.
|
||||||
|
- **`human-merge` label** (together with `autopilot`): everything above except the merge — after
|
||||||
|
`@qa` approves, `@pm` tells the creator the PR is ready and a human merges.
|
||||||
No label (the default) = today's behavior, unchanged.
|
No label (the default) = today's behavior, unchanged.
|
||||||
|
|
||||||
## Branches & pull requests
|
## Branches & pull requests
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ deploy-host SSH skill, live in the consuming repo under `.gitea/agent-skills/`
|
|||||||
4. `@qa` reviews **on the PR** — either recommendations + `BOUNCE: @dev` (dev fixes → `@qa`
|
4. `@qa` reviews **on the PR** — either recommendations + `BOUNCE: @dev` (dev fixes → `@qa`
|
||||||
re-verifies, direct loop, max 3 rounds) or `APPROVE`.
|
re-verifies, direct loop, max 3 rounds) or `APPROVE`.
|
||||||
5. On approval `@qa` posts the verdict **on the issue** → `@pm` tells the creator *"ready to merge"*
|
5. On approval `@qa` posts the verdict **on the issue** → `@pm` tells the creator *"ready to merge"*
|
||||||
and a **human merges** — or, with the `autopilot` label, `@pm` merges and closes the issue itself.
|
and a **human merges** — or, with the `autopilot` label, `@pm` merges and closes the issue itself
|
||||||
|
(unless the issue also carries `human-merge`, which keeps the merge with a human).
|
||||||
|
|
||||||
`@pm` is the only agent that ever merges, and only under the `autopilot` label (its kill switch:
|
`@pm` is the only agent that ever merges, and only under the `autopilot` label (its kill switch:
|
||||||
remove the label mid-flight and the next step reverts to human control).
|
remove the label mid-flight and the next step reverts to human control).
|
||||||
@@ -87,7 +88,12 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
agent:
|
agent:
|
||||||
uses: gitea/agents/.gitea/workflows/agent.yml@main
|
uses: gitea/agents/.gitea/workflows/agent.yml@main
|
||||||
secrets: inherit
|
# Explicit map, not `secrets: inherit`: inherit alone has left XAI_API_KEY empty on some Gitea
|
||||||
|
# cross-owner calls. Copy the full list from .gitea/workflows/ai-agent.yml, which is the stub
|
||||||
|
# this file abbreviates -- a secret missing from the map does not reach the workflow at all.
|
||||||
|
secrets:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
# ... and the OLLAMA_*, XAI_API_KEY, TOKEN_* and caller-skill entries, verbatim.
|
||||||
```
|
```
|
||||||
|
|
||||||
That's the whole per-repo footprint, and it's the minimum a caller can be: the `on:` triggers must
|
That's the whole per-repo footprint, and it's the minimum a caller can be: the `on:` triggers must
|
||||||
|
|||||||
Reference in new issue
Block a user