#!/usr/bin/env bash # Run the agent: build the full prompt, invoke opencode with retries, and reconstruct the # plain-text reply (/tmp/agent_out.md) plus the raw event stream (/tmp/events.jsonl). # # Required env (provided by the workflow step): # ANTHROPIC_API_KEY AGENT_TOKEN NAME MODEL VISION MODE HAS_IMAGES BRANCH NUM TITLE IBODY CMT # FILES (the opencode -f image flags, from the imgs step output) set -u [ -z "$CMT" ] && CMT="(a new issue was just opened — assess it)" THREAD=$(cat /tmp/thread.md 2>/dev/null); [ -z "$THREAD" ] && THREAD="(no prior comments)" DESC=$(jq -r --arg a "$NAME" '.[$a].desc' /tmp/agents.json) # Include each teammate's skills so an agent (esp. @pm) can route by capability — e.g. only # @senior/@lead hold node1-ssh, so a node1 task must not go to @junior. Skill *names* only; the # scoped how-to detail stays hidden per the permission.skill allow-list. ROSTER=$(jq -r 'to_entries | map("- @\(.key): \(.value.desc) (vision: \(.value.vision); skills: \(.value.skills | if length>0 then join(", ") else "none" end))") | join("\n")' /tmp/agents.json) if [ "$VISION" = "true" ]; then CAP="You CAN read images attached to the issue."; else CAP="You CANNOT read images — you are a text-only model."; fi NOTE="" if [ "$VISION" != "true" ] && [ "${HAS_IMAGES:-0}" -gt 0 ]; then NOTE="IMPORTANT: this issue has image attachment(s) you cannot read. Do NOT guess their contents — say so and tell the maintainer to re-run with a vision-capable teammate (@senior, @lead, or @pm)." fi 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: @' (one of @junior @senior @lead @qa) — 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. To CLOSE the issue (the maintainer says it is not needed / a duplicate / won't-do), briefly note why and end your reply with EXACTLY one line: 'CLOSE_ISSUE'. Only close when clearly instructed or it is obviously not needed; when in doubt, ask instead." if [ "$NAME" = "pm" ]; then ACTION="$ACTION As PM you work in two phases and NEVER skip the approval gate: PLAN — when the task is clear, present a SHORT plan naming which teammate should build it (@junior for small/low-risk, @senior/@lead for complex, @qa to verify), then END by asking '@ffaerber ready to start building? reply yes to proceed.' Do NOT include a DELEGATE line yet. DELEGATE — ONLY after the maintainer has explicitly approved starting in the thread (a clear 'yes' / 'go' / 'proceed' / 'start building' answering your ready-to-build question) do you end your reply with a 'DELEGATE: @' line to hand off. Never present a plan and delegate on the same turn. If anything is unclear or needs a decision, START your reply with '@ffaerber', ask specific questions, and do NOT delegate. BREAKDOWN (for a feature too big for one PR): first PLAN — propose a milestone name and the list of sub-tasks (title + one line each), then ask '@ffaerber create these N sub-issues? reply yes.' Do NOT emit the block yet. ONLY after the maintainer approves, end your reply with EXACTLY: BEGIN_SUBTASKS milestone: - :: - :: END_SUBTASKS The automation creates the milestone + one sub-issue per line (each linked to this issue). It does NOT auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready." fi else ACTION="You start on git branch '${BRANCH}', with git and push credentials already configured. FIRST read AGENTS.md at the repo root and FOLLOW IT EXACTLY — it defines the golden rules, branch naming, how to split work into multiple small independently-mergeable PRs, commit/push style, and the required PR-description format (the BEGIN_PR_DESCRIPTION block the automation extracts). Do all work on branches (never in the issue), commit and push as you go, and do NOT open pull requests yourself — that is automated for every branch you push. If the task is genuinely unclear, make NO changes and reply with specific questions instead." fi PROMPT="You are @${NAME}, a member of an AI dev team working on this Gitea repository. YOUR ROLE: ${DESC} YOUR CAPABILITIES: model ${MODEL}. ${CAP} ${NOTE} TEAM ROSTER (who does what — hand off if a task isn't yours): ${ROSTER} The `gitea-api` skill carries a LIVE Agent Capability Matrix (auto-introspected at workflow start via GET /api/v1/token) listing every teammate's real Gitea scopes — load that skill if you need to route by what an agent can actually do on Gitea. ${ACTION} If a task needs expertise or a capability you lack, do NOT guess — say which teammate should handle it. The task is fully described below; do not search the repo for an 'issue' file. TASK (issue #${NUM} \"${TITLE}\"): ${IBODY} FULL CONVERSATION THREAD SO FAR (every comment on this issue, oldest first — including your OWN previous replies and the maintainer's answers). READ IT CAREFULLY. Do NOT repeat questions that have already been answered; build on what has already been decided. If the maintainer has answered your earlier questions, ACT on those answers — do not re-ask. ${THREAD} LATEST INSTRUCTION FROM MAINTAINER: ${CMT}" echo "opencode version: $(opencode --version 2>&1)" # Capture the raw JSON event stream (--format json) so the activity log can be built # from it afterwards. The plain --auto reply text == concatenation of all assistant # "text" parts, so reconstruct /tmp/agent_out.md from those — the Publish step below # keeps reading agent_out.md exactly as before. Success is exit code 0: the agent may # make tool-only changes with no text summary, so DO NOT treat empty output as failure. rc=1 for attempt in 1 2 3; do echo "opencode attempt $attempt/3 for @$NAME ($MODEL)" rc=0 opencode run --model "$MODEL" --auto --format json "$PROMPT" ${FILES:-} \ >/tmp/events.jsonl 2>/tmp/agent_err.log || rc=$? echo "rc=$rc"; echo "--- events ($(wc -l < /tmp/events.jsonl 2>/dev/null || echo 0) lines) ---" echo "--- stderr (trace) ---"; cat /tmp/agent_err.log [ $rc -eq 0 ] && break if grep -qiE 'overloaded|429|529|rate.?limit|timeout|ETIMEDOUT|ECONNRESET|EAI_AGAIN' /tmp/events.jsonl /tmp/agent_err.log; then echo "transient error — backing off $((attempt*20))s"; sleep $((attempt * 20)); continue fi echo "non-transient failure (rc=$rc) — not retrying"; break done [ $rc -eq 0 ] || { echo "agent failed"; exit 1; } # Reconstruct the plain-text reply from assistant text parts (== what plain --auto prints). jq -r 'select(.type=="text") | .part.text // ""' /tmp/events.jsonl > /tmp/agent_out.md 2>/dev/null || true echo "reconstructed reply ($(wc -l < /tmp/agent_out.md 2>/dev/null || echo 0) lines):"; cat /tmp/agent_out.md