mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
tools/buzz-agent/agent-exec.sh — the last mile from the scheduler to a live channel. Honors the `amy buzz agent serve --exec` contract: reads the task on stdin, runs a coding agent (Claude Code by default, or any $AGENT_CMD) inside the job's git worktree, verifies a diff exists, commits, pushes the `claude/job-*` feature branch, opens (or reuses) a PR, and prints the PR URL as the job result (kind-43004); any failure exits non-zero → job error (kind-43006). It never touches the default branch and never force-pushes — merge stays a human action on GitHub. README documents the load-bearing guardrails, since Buzz enforces none of them: a PR-only fine-grained token (Contents + Pull requests write, nothing else), branch protection on the default branch (require PR + review + CI, block force-push/deletion), and scoped intake (--accept-from) + agent tool allowlist. Verified end-to-end against a throwaway repo with a stubbed gh + agent (happy path pushes the branch and prints the PR URL; no-change path errors cleanly). Plan doc follow-up #3 marked done. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mApqAbr8vkLC7gUDjavu6
105 lines
5.0 KiB
Bash
Executable File
105 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# agent-exec.sh — reference `--exec` wrapper for `amy buzz agent serve`.
|
|
#
|
|
# The scheduler runs this once per Buzz job (kind-43001). Contract:
|
|
# stdin ............ the task text (the job request)
|
|
# cwd .............. the job's isolated git worktree (a fresh branch off --base-ref)
|
|
# env .............. BUZZ_JOB_ID BUZZ_REQUESTER BUZZ_CHANNEL BUZZ_RELAY BUZZ_AGENT
|
|
# BUZZ_UPVOTES BUZZ_BRANCH BUZZ_WORKTREE BUZZ_BASE_REF
|
|
# stdout ........... becomes the job RESULT (kind-43004) — we print the PR URL
|
|
# non-zero exit .... becomes the job ERROR (kind-43006); stderr is the error detail
|
|
#
|
|
# What it does: runs a coding agent (Claude Code by default) on the task inside the
|
|
# worktree, commits + pushes the job branch, opens a PR, and prints the PR URL. It NEVER
|
|
# touches the default branch and NEVER force-pushes — the merge is a human action on
|
|
# GitHub. See README.md for the required token scope + branch-protection checklist.
|
|
#
|
|
# Host requirements: git, gh (authenticated with a PR-ONLY token), and the agent CLI
|
|
# (`claude`), or a custom command via $AGENT_CMD.
|
|
|
|
set -euo pipefail
|
|
|
|
log() { printf '%s\n' "$*" >&2; } # progress → stderr (job-error tail on failure)
|
|
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
|
|
|
|
# --- config knobs (all overridable via env) --------------------------------
|
|
AGENT_CMD="${AGENT_CMD:-}" # override the entire agent invocation; reads the
|
|
# prompt on stdin and as $AGENT_PROMPT
|
|
AGENT_ALLOWED_TOOLS="${AGENT_ALLOWED_TOOLS:-Edit,Write,Read,Bash,Glob,Grep}"
|
|
COMMIT_PREFIX="${COMMIT_PREFIX:-feat}"
|
|
|
|
# --- guards ----------------------------------------------------------------
|
|
[[ -n "${BUZZ_BRANCH:-}" ]] || die "BUZZ_BRANCH unset — run the scheduler with --worktree"
|
|
[[ -n "${BUZZ_WORKTREE:-}" ]] || die "BUZZ_WORKTREE unset — run the scheduler with --worktree"
|
|
cd "$BUZZ_WORKTREE" || die "cannot cd into worktree $BUZZ_WORKTREE"
|
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "worktree is not a git repo"
|
|
|
|
# The PR base = the repo's default branch. Never operate on it directly.
|
|
base_branch="$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo main)"
|
|
case "$BUZZ_BRANCH" in
|
|
"$base_branch" | main | master) die "refusing to operate on the default branch ($BUZZ_BRANCH)" ;;
|
|
esac
|
|
|
|
# Pin the branch's starting commit now, so "did the agent change anything?" is correct even
|
|
# when --base-ref is the symbolic "HEAD" (which moves as the agent commits).
|
|
base_sha="$(git rev-parse HEAD)"
|
|
|
|
# --- 1. read the task ------------------------------------------------------
|
|
task="$(cat)"
|
|
[[ -n "${task//[[:space:]]/}" ]] || die "empty task"
|
|
title="$(printf '%s' "$task" | head -n1 | cut -c1-72)"
|
|
|
|
# --- 2. run the coding agent inside the worktree ---------------------------
|
|
log "[agent-exec] job ${BUZZ_JOB_ID:-?}: running agent on: $title"
|
|
prompt="You are working in a fresh git worktree on branch '$BUZZ_BRANCH' (off '${BUZZ_BASE_REF:-HEAD}').
|
|
Implement the request below and then stop. Do NOT switch branches, push, or open a PR — the
|
|
wrapper handles git. Keep changes scoped to this repository.
|
|
|
|
TASK:
|
|
$task"
|
|
|
|
if [[ -n "$AGENT_CMD" ]]; then
|
|
export AGENT_PROMPT="$prompt"
|
|
agent_summary="$(printf '%s' "$prompt" | bash -c "$AGENT_CMD" 2>&1)" || die "agent command failed"
|
|
else
|
|
command -v claude >/dev/null 2>&1 || die "claude CLI not found (set AGENT_CMD to your agent)"
|
|
agent_summary="$(claude -p "$prompt" --permission-mode acceptEdits --allowedTools "$AGENT_ALLOWED_TOOLS" 2>&1)" ||
|
|
die "claude run failed"
|
|
fi
|
|
|
|
# --- 3. verify the agent produced changes ----------------------------------
|
|
committed="$(git rev-list --count "$base_sha"..HEAD 2>/dev/null || echo 0)"
|
|
if [[ -z "$(git status --porcelain)" && "$committed" == "0" ]]; then
|
|
die "the agent produced no changes"
|
|
fi
|
|
|
|
# --- 4. commit anything the agent left uncommitted -------------------------
|
|
if [[ -n "$(git status --porcelain)" ]]; then
|
|
git add -A
|
|
git -c user.name="Buzz Agent" -c user.email="agent@localhost" commit -q \
|
|
-m "$COMMIT_PREFIX: $title" -m "Filed via Buzz job ${BUZZ_JOB_ID:-unknown}."
|
|
fi
|
|
|
|
# --- 5. push the job branch (feature branch only, never --force) -----------
|
|
log "[agent-exec] pushing $BUZZ_BRANCH"
|
|
git push -u origin "HEAD:$BUZZ_BRANCH" || die "push failed (is a PR-only token configured?)"
|
|
|
|
# --- 6. open (or reuse) the PR ---------------------------------------------
|
|
pr_url="$(gh pr list --head "$BUZZ_BRANCH" --state open --json url -q '.[0].url' 2>/dev/null || true)"
|
|
if [[ -z "$pr_url" ]]; then
|
|
body="$task
|
|
|
|
---
|
|
Filed via Buzz job \`${BUZZ_JOB_ID:-unknown}\`${BUZZ_REQUESTER:+ by \`$BUZZ_REQUESTER\`}.
|
|
|
|
Agent notes:
|
|
|
|
$agent_summary"
|
|
pr_url="$(gh pr create --base "$base_branch" --head "$BUZZ_BRANCH" --title "$title" --body "$body" 2>/dev/null)" ||
|
|
die "gh pr create failed (PR-only token + branch protection configured?)"
|
|
fi
|
|
|
|
# --- 7. emit the result → job 43004 ----------------------------------------
|
|
printf 'Opened PR: %s\n' "$pr_url"
|