Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ea9f68c9 | ||
|
|
6c753dc0a0 | ||
|
|
23c48e66c4 |
@@ -1,6 +1,6 @@
|
|||||||
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: ffaerber/agents/.gitea/workflows/agent.yml@main (secrets: inherit).
|
# and invokes this via: uses: gitea/agents/.gitea/workflows/agent.yml@main (secrets: inherit).
|
||||||
# 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:
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
- name: Fetch shared agent scripts (this repo)
|
- name: Fetch shared agent scripts (this repo)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ffaerber/agents
|
repository: gitea/agents
|
||||||
ref: main
|
ref: main
|
||||||
path: .agents-workflow
|
path: .agents-workflow
|
||||||
token: ${{ secrets.GITEA_TOKEN }}
|
token: ${{ secrets.GITEA_TOKEN }}
|
||||||
@@ -135,12 +135,13 @@ jobs:
|
|||||||
- name: Set up `gitea-admin` skill (@ops only — administer the Gitea instance)
|
- name: Set up `gitea-admin` skill (@ops only — administer the Gitea instance)
|
||||||
# Instance administration (orgs/users/repos/labels/secrets/scoped tokens). The SKILL.md is
|
# Instance administration (orgs/users/repos/labels/secrets/scoped tokens). The SKILL.md is
|
||||||
# written ONLY for @ops (skill-gitea-admin.sh gates on NAME), so the admin how-to never
|
# written ONLY for @ops (skill-gitea-admin.sh gates on NAME), so the admin how-to never
|
||||||
# reaches other agents; permission.skill also denies it to everyone but @ops. Uses
|
# reaches other agents; permission.skill also denies it to everyone but @ops. Uses the
|
||||||
# AGENT_TOKEN (an admin PAT during bootstrap) — see the script header for the token plan.
|
# dedicated admin token TOKEN_OPS (the `ops` user), NOT AGENT_TOKEN — this step only writes
|
||||||
|
# the doc; the token itself is injected into the Run-agent step only when NAME==ops.
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
NAME: ${{ steps.prep.outputs.name }}
|
NAME: ${{ steps.prep.outputs.name }}
|
||||||
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
|
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
|
||||||
run: bash "$SCRIPTS/skill-gitea-admin.sh"
|
run: bash "$SCRIPTS/skill-gitea-admin.sh"
|
||||||
|
|
||||||
- name: Inspect / fetch image attachments (download only for vision agents)
|
- name: Inspect / fetch image attachments (download only for vision agents)
|
||||||
@@ -168,6 +169,9 @@ jobs:
|
|||||||
# It is already a required secret for the delegation step below; exposing it here too
|
# It is already a required secret for the delegation step below; exposing it here too
|
||||||
# lets the agent process itself call the Gitea API on demand.
|
# lets the agent process itself call the Gitea API on demand.
|
||||||
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
|
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
|
||||||
|
# The dedicated admin token for the `gitea-admin` skill — injected into the agent's process
|
||||||
|
# ONLY when it is @ops, so no other agent ever holds an admin credential in its env.
|
||||||
|
TOKEN_OPS: ${{ steps.prep.outputs.name == 'ops' && secrets.TOKEN_OPS || '' }}
|
||||||
NAME: ${{ steps.prep.outputs.name }}
|
NAME: ${{ steps.prep.outputs.name }}
|
||||||
MODEL: ${{ steps.prep.outputs.model }}
|
MODEL: ${{ steps.prep.outputs.model }}
|
||||||
VISION: ${{ steps.prep.outputs.vision }}
|
VISION: ${{ steps.prep.outputs.vision }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: ai-agent
|
name: ai-agent
|
||||||
run-name: "ai-agent · #${{ github.event.issue.number }}" # quotes required: bare # starts a YAML comment
|
run-name: "ai-agent · #${{ github.event.issue.number }}" # quotes required: bare # starts a YAML comment
|
||||||
# Standard caller for the shared AI-agent workflow (ffaerber/agents). Copy this file VERBATIM into
|
# Standard caller for the shared AI-agent workflow (gitea/agents). Copy this file VERBATIM into
|
||||||
# any repo that should get the agents — it is identical in every repo. All logic + scripts live in
|
# any repo that should get the agents — it is identical in every repo. All logic + scripts live in
|
||||||
# agents/.gitea/workflows/; scripts are fetched from @main at run time. The `jobs.agent` wrapper is
|
# agents/.gitea/workflows/; scripts are fetched from @main at run time. The `jobs.agent` wrapper is
|
||||||
# required: a reusable (workflow_call) workflow can only be invoked from a caller job, not top-level.
|
# required: a reusable (workflow_call) workflow can only be invoked from a caller job, not top-level.
|
||||||
@@ -11,5 +11,5 @@ on:
|
|||||||
types: [opened]
|
types: [opened]
|
||||||
jobs:
|
jobs:
|
||||||
agent:
|
agent:
|
||||||
uses: ffaerber/agents/.gitea/workflows/agent.yml@main
|
uses: gitea/agents/.gitea/workflows/agent.yml@main
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
# Emits an opencode Skill file under ~/.config/opencode/skills/ documenting how to create
|
# Emits an opencode Skill file under ~/.config/opencode/skills/ documenting how to create
|
||||||
# orgs/users/repos, manage labels & secrets, and mint scoped per-user tokens via the Gitea API.
|
# orgs/users/repos, manage labels & secrets, and mint scoped per-user tokens via the Gitea API.
|
||||||
#
|
#
|
||||||
# The credential is AGENT_TOKEN (BOOTSTRAP: currently an admin PAT — temporary). This skill doc is
|
# The credential is TOKEN_OPS (BOOTSTRAP: currently an admin PAT — temporary). This skill doc is
|
||||||
# written ONLY for @ops (gated on NAME) so the how-to never reaches other agents. NOTE: while
|
# written ONLY for @ops (gated on NAME) so the how-to never reaches other agents. NOTE: while
|
||||||
# AGENT_TOKEN is admin, every agent's process technically holds an admin credential in its env —
|
# TOKEN_OPS is admin, every agent's process technically holds an admin credential in its env —
|
||||||
# that is the bootstrap trade-off. Once @ops is minting scoped per-user tokens, AGENT_TOKEN should be
|
# that is the bootstrap trade-off. Once @ops is minting scoped per-user tokens, TOKEN_OPS should be
|
||||||
# narrowed and a dedicated admin token injected only for @ops.
|
# narrowed and a dedicated admin token injected only for @ops.
|
||||||
#
|
#
|
||||||
# Required env (provided by the workflow step): NAME AGENT_TOKEN
|
# Required env (provided by the workflow step): NAME TOKEN_OPS
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
[ "${NAME:-}" = "ops" ] || { echo "not @ops — skipping gitea-admin skill"; exit 0; }
|
[ "${NAME:-}" = "ops" ] || { echo "not @ops — skipping gitea-admin skill"; exit 0; }
|
||||||
if [ -z "${AGENT_TOKEN:-}" ]; then
|
if [ -z "${TOKEN_OPS:-}" ]; then
|
||||||
echo "AGENT_TOKEN not set — skipping gitea-admin skill"
|
echo "TOKEN_OPS not set — skipping gitea-admin skill"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
mkdir -p ~/.config/opencode/skills/gitea-admin && chmod 700 ~/.config/opencode/skills/gitea-admin
|
mkdir -p ~/.config/opencode/skills/gitea-admin && chmod 700 ~/.config/opencode/skills/gitea-admin
|
||||||
@@ -29,7 +29,7 @@ tags: [gitea, admin, api, curl, bootstrap]
|
|||||||
# `gitea-admin` Skill (operator / @ops only)
|
# `gitea-admin` Skill (operator / @ops only)
|
||||||
|
|
||||||
Administer the Gitea instance via its REST API at `${GITHUB_SERVER_URL}/api/v1`, authenticated with
|
Administer the Gitea instance via its REST API at `${GITHUB_SERVER_URL}/api/v1`, authenticated with
|
||||||
`Authorization: token ${AGENT_TOKEN}` (a site-admin token during bootstrap). Both env vars are
|
`Authorization: token ${TOKEN_OPS}` (a site-admin token during bootstrap). Both env vars are
|
||||||
already set. Work from the issue instructions; report what you did.
|
already set. Work from the issue instructions; report what you did.
|
||||||
|
|
||||||
## Golden rules
|
## Golden rules
|
||||||
@@ -42,7 +42,7 @@ already set. Work from the issue instructions; report what you did.
|
|||||||
|
|
||||||
## Create an organisation
|
## Create an organisation
|
||||||
```
|
```
|
||||||
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \
|
curl -sS -X POST -H "Authorization: token $TOKEN_OPS" -H "Content-Type: application/json" \
|
||||||
"$API/orgs" -d '{"username":"acme","visibility":"private"}'
|
"$API/orgs" -d '{"username":"acme","visibility":"private"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,14 +53,14 @@ password you just set) to mint a scoped token, and store the token straight into
|
|||||||
API="${GITHUB_SERVER_URL}/api/v1"
|
API="${GITHUB_SERVER_URL}/api/v1"
|
||||||
PW=$(head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 24) # generated, never printed
|
PW=$(head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 24) # generated, never printed
|
||||||
# 1) create the user
|
# 1) create the user
|
||||||
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \
|
curl -sS -X POST -H "Authorization: token $TOKEN_OPS" -H "Content-Type: application/json" \
|
||||||
"$API/admin/users" -d "$(jq -nc --arg u inter --arg e inter@ffaerber.duckdns.org --arg p "$PW" \
|
"$API/admin/users" -d "$(jq -nc --arg u inter --arg e inter@ffaerber.duckdns.org --arg p "$PW" \
|
||||||
'{username:$u,email:$e,password:$p,must_change_password:false,source_id:0,visibility:"private"}')"
|
'{username:$u,email:$e,password:$p,must_change_password:false,source_id:0,visibility:"private"}')"
|
||||||
# 2) mint a scoped token AS that user (pick the narrowest scopes needed)
|
# 2) mint a scoped token AS that user (pick the narrowest scopes needed)
|
||||||
tok=$(curl -sS -u "inter:$PW" -H "Content-Type: application/json" -X POST "$API/users/inter/tokens" \
|
tok=$(curl -sS -u "inter:$PW" -H "Content-Type: application/json" -X POST "$API/users/inter/tokens" \
|
||||||
-d '{"name":"inter","scopes":["read:repository","write:issue"]}' | jq -r '.sha1')
|
-d '{"name":"inter","scopes":["read:repository","write:issue"]}' | jq -r '.sha1')
|
||||||
# 3) store the value in BOTH places (see "Secret storage" below) — never print $tok
|
# 3) store the value in BOTH places (see "Secret storage" below) — never print $tok
|
||||||
curl -sS -X PUT -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \
|
curl -sS -X PUT -H "Authorization: token $TOKEN_OPS" -H "Content-Type: application/json" \
|
||||||
"$API/orgs/gitea/actions/secrets/TOKEN_INTER" -d "$(jq -nc --arg d "$tok" '{data:$d}')"
|
"$API/orgs/gitea/actions/secrets/TOKEN_INTER" -d "$(jq -nc --arg d "$tok" '{data:$d}')"
|
||||||
```
|
```
|
||||||
Token **scopes** are groups of `read:`/`write:` on: `repository`, `issue`, `organization`, `user`,
|
Token **scopes** are groups of `read:`/`write:` on: `repository`, `issue`, `organization`, `user`,
|
||||||
@@ -84,23 +84,23 @@ then overwrite the stored secret.
|
|||||||
```
|
```
|
||||||
curl -sS -u "inter:$PW" -X DELETE "$API/users/inter/tokens/<name-or-id>" # needs the password again
|
curl -sS -u "inter:$PW" -X DELETE "$API/users/inter/tokens/<name-or-id>" # needs the password again
|
||||||
tok=$(curl -sS -u "inter:$PW" -X POST "$API/users/inter/tokens" -d '{"name":"inter","scopes":[…new…]}' | jq -r '.sha1')
|
tok=$(curl -sS -u "inter:$PW" -X POST "$API/users/inter/tokens" -d '{"name":"inter","scopes":[…new…]}' | jq -r '.sha1')
|
||||||
curl -sS -X PUT -H "Authorization: token $AGENT_TOKEN" "$API/orgs/gitea/actions/secrets/TOKEN_INTER" -d "$(jq -nc --arg d "$tok" '{data:$d}')"
|
curl -sS -X PUT -H "Authorization: token $TOKEN_OPS" "$API/orgs/gitea/actions/secrets/TOKEN_INTER" -d "$(jq -nc --arg d "$tok" '{data:$d}')"
|
||||||
```
|
```
|
||||||
(If you no longer hold the user's password, reset it first via `PATCH /admin/users/{username}` with a
|
(If you no longer hold the user's password, reset it first via `PATCH /admin/users/{username}` with a
|
||||||
new generated password, then re-mint.)
|
new generated password, then re-mint.)
|
||||||
|
|
||||||
## Actions secrets & variables
|
## Actions secrets & variables
|
||||||
```
|
```
|
||||||
curl -sS -X PUT -H "Authorization: token $AGENT_TOKEN" "$API/orgs/{org}/actions/secrets/{NAME}" -d '{"data":"<value>"}'
|
curl -sS -X PUT -H "Authorization: token $TOKEN_OPS" "$API/orgs/{org}/actions/secrets/{NAME}" -d '{"data":"<value>"}'
|
||||||
curl -sS -X PUT -H "Authorization: token $AGENT_TOKEN" "$API/repos/{owner}/{repo}/actions/secrets/{NAME}" -d '{"data":"<value>"}'
|
curl -sS -X PUT -H "Authorization: token $TOKEN_OPS" "$API/repos/{owner}/{repo}/actions/secrets/{NAME}" -d '{"data":"<value>"}'
|
||||||
curl -sS -X PUT -H "Authorization: token $AGENT_TOKEN" "$API/user/actions/secrets/{NAME}" -d '{"data":"<value>"}' # user-level
|
curl -sS -X PUT -H "Authorization: token $TOKEN_OPS" "$API/user/actions/secrets/{NAME}" -d '{"data":"<value>"}' # user-level
|
||||||
```
|
```
|
||||||
|
|
||||||
## Labels (repo or org-wide). Scoped labels (name `scope/value`) are mutually exclusive if `exclusive:true`.
|
## Labels (repo or org-wide). Scoped labels (name `scope/value`) are mutually exclusive if `exclusive:true`.
|
||||||
```
|
```
|
||||||
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" "$API/repos/{owner}/{repo}/labels" \
|
curl -sS -X POST -H "Authorization: token $TOKEN_OPS" "$API/repos/{owner}/{repo}/labels" \
|
||||||
-d '{"name":"status/review","color":"1d76db","description":"…","exclusive":true}'
|
-d '{"name":"status/review","color":"1d76db","description":"…","exclusive":true}'
|
||||||
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" "$API/orgs/{org}/labels" -d '{…}'
|
curl -sS -X POST -H "Authorization: token $TOKEN_OPS" "$API/orgs/{org}/labels" -d '{…}'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bootstrap a new repo (create + wire it up for the agents)
|
## Bootstrap a new repo (create + wire it up for the agents)
|
||||||
@@ -109,7 +109,7 @@ curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" "$API/orgs/{org}/labels"
|
|||||||
3. Commit the standard caller so it gets the agents — `PUT /repos/{owner}/{repo}/contents/.gitea/workflows/ai-agent.yml`
|
3. Commit the standard caller so it gets the agents — `PUT /repos/{owner}/{repo}/contents/.gitea/workflows/ai-agent.yml`
|
||||||
with base64 `content`, `message`, `branch:"main"` (copy the exact caller from the `agents` repo README).
|
with base64 `content`, `message`, `branch:"main"` (copy the exact caller from the `agents` repo README).
|
||||||
4. Add the agent bot users as collaborators: `PUT /repos/{owner}/{repo}/collaborators/{username}` (`{"permission":"write"}`).
|
4. Add the agent bot users as collaborators: `PUT /repos/{owner}/{repo}/collaborators/{username}` (`{"permission":"write"}`).
|
||||||
5. Ensure the repo can run agents — the org must hold the runtime secrets (ANTHROPIC_API_KEY, AGENT_TOKEN,
|
5. Ensure the repo can run agents — the org must hold the runtime secrets (ANTHROPIC_API_KEY, TOKEN_OPS,
|
||||||
TOKEN_* , OLLAMA_URL, OLLAMA_CLOUD_API_KEY); set any missing via the secrets calls above.
|
TOKEN_* , OLLAMA_URL, OLLAMA_CLOUD_API_KEY); set any missing via the secrets calls above.
|
||||||
|
|
||||||
## Admin user management
|
## Admin user management
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ it is the source of truth, and `agents` itself uses the same file:
|
|||||||
```yaml
|
```yaml
|
||||||
name: ai-agent
|
name: ai-agent
|
||||||
run-name: "ai-agent · #${{ github.event.issue.number }}" # quotes required: bare # starts a YAML comment
|
run-name: "ai-agent · #${{ github.event.issue.number }}" # quotes required: bare # starts a YAML comment
|
||||||
# Standard caller for the shared AI-agent workflow (ffaerber/agents). Copy this file VERBATIM into
|
# Standard caller for the shared AI-agent workflow (gitea/agents). Copy this file VERBATIM into
|
||||||
# any repo that should get the agents — it is identical in every repo. All logic + scripts live in
|
# any repo that should get the agents — it is identical in every repo. All logic + scripts live in
|
||||||
# agents/.gitea/workflows/; scripts are fetched from @main at run time. The `jobs.agent` wrapper is
|
# agents/.gitea/workflows/; scripts are fetched from @main at run time. The `jobs.agent` wrapper is
|
||||||
# required: a reusable (workflow_call) workflow can only be invoked from a caller job, not top-level.
|
# required: a reusable (workflow_call) workflow can only be invoked from a caller job, not top-level.
|
||||||
@@ -49,7 +49,7 @@ on:
|
|||||||
types: [opened]
|
types: [opened]
|
||||||
jobs:
|
jobs:
|
||||||
agent:
|
agent:
|
||||||
uses: ffaerber/agents/.gitea/workflows/agent.yml@main
|
uses: gitea/agents/.gitea/workflows/agent.yml@main
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user