feat(agent-queue): per-repo verify + opt-in auto-merge for PR jobs

Claim now carries verify (drives the existing verify gate -> PR opens only if it
passes) and autoMerge (squash-merge via gh pr merge after the PR opens, non-fatal).
selftest covers both.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-05-31 06:17:28 -07:00
parent e634d4915f
commit b442b95728
3 changed files with 30 additions and 7 deletions

View File

@ -709,6 +709,17 @@ _fleet_pr_open() {
printf '%s' "$url" | tr -d '\n'
}
# Auto-merge a PR (opt-in via the job's autoMerge flag). Squash-merges and deletes
# the branch; non-fatal — a failure (e.g. branch protection / required checks) leaves
# the PR open. Returns 0 on merge.
_fleet_pr_merge() {
local dir=$1 url=$2 logf=$3
if ( cd "$dir" && "$GH_BIN" pr merge "$url" --squash --delete-branch >>"$logf" 2>&1 ); then
echo "PR auto-merged: $url" >>"$logf"; return 0
fi
echo "PR: auto-merge failed — left open: $url" >>"$logf"; return 1
}
# ── Worker: runs one job to completion (invoked in background) ───────
run_worker() {
local doing_file=$1
@ -796,10 +807,11 @@ run_worker() {
# agent in an isolated checkout on branch aq/job/<id> instead of the static
# cwd. The PR is opened after a passing verify. WIP checkpointing is skipped
# for PR jobs — the pushed PR branch is the durable artifact. ──
local pr_dir="" pr_base="" pr_repo="" pr_jid=""
local pr_dir="" pr_base="" pr_repo="" pr_jid="" pr_automerge=""
if [[ "${AQ_FLEET_PR:-0}" == 1 ]] && fleet_enabled && _fleet_is_job "$job"; then
pr_repo=$(fm_get "$doing_file" fleet-repo "")
pr_base=$(fm_get "$doing_file" fleet-base-branch "main")
pr_automerge=$(fm_get "$doing_file" fleet-automerge "")
# Branch off the stable fleet job id (not the transient local job name).
pr_jid=$(fm_get "$doing_file" fleet-job-id "$job")
if [[ -n "$pr_repo" ]]; then
@ -966,6 +978,8 @@ run_worker() {
if [[ -n "$_prurl" ]]; then
{ echo "pr_url=$_prurl"; echo "pr_branch=aq/job/$pr_jid"; } >> "$metaf"
echo "PR opened: $_prurl" >> "$logf"
# Opt-in auto-merge (job autoMerge flag): squash-merge the PR now.
[[ "$pr_automerge" == "true" ]] && _fleet_pr_merge "$pr_dir" "$_prurl" "$logf"
if fleet_enabled && _fleet_is_job "$job"; then fleet_report_insights "$job" testing; fi
fi
fi

View File

@ -185,12 +185,14 @@ fleet_claim() {
case "$FLEET_CODE" in 2*) :;; *) err "fleet: claim failed (HTTP ${FLEET_CODE:-error})"; return 1;; esac
printf '%s' "$FLEET_BODY" | grep -q '"claimed"[[:space:]]*:[[:space:]]*true' || return 2
local jid body_md epoch repo base_branch
local jid body_md epoch repo base_branch verify automerge=""
jid=$(printf '%s' "$FLEET_BODY" | _json_str id)
body_md=$(printf '%s' "$FLEET_BODY" | _json_str bodyMd)
epoch=$(printf '%s' "$FLEET_BODY" | _fleet_json_num leaseEpoch)
repo=$(printf '%s' "$FLEET_BODY" | _json_str repo)
base_branch=$(printf '%s' "$FLEET_BODY" | _json_str baseBranch)
verify=$(printf '%s' "$FLEET_BODY" | _json_str verify)
printf '%s' "$FLEET_BODY" | grep -q '"autoMerge"[[:space:]]*:[[:space:]]*true' && automerge=true
[[ -n "$jid" ]] || { err "fleet: claim returned no job id"; return 1; }
# Materialize a transient local job .md (same approach as from-tracker) so the
@ -208,6 +210,9 @@ fleet_claim() {
echo "fleet-lease-epoch: ${epoch:-0}"
[[ -n "$repo" ]] && echo "fleet-repo: $repo"
[[ -n "$base_branch" ]] && echo "fleet-base-branch: $base_branch"
# Per-repo verify command (drives the existing verify gate) + auto-merge flag.
[[ -n "$verify" ]] && echo "verify: $verify"
[[ -n "$automerge" ]] && echo "fleet-automerge: true"
echo "idempotency-key: fleet-$jid"
echo "---"
echo

View File

@ -775,6 +775,8 @@ case "$1 $2" in
[ -n "${AQ_FSTUB_CLAIM_FLAG:-}" ] && : > "$AQ_FSTUB_CLAIM_FLAG"
repo_field=""
[ -n "${AQ_FSTUB_REPO:-}" ] && repo_field=",\"repo\":\"${AQ_FSTUB_REPO}\",\"baseBranch\":\"${AQ_FSTUB_BASE:-main}\""
[ -n "${AQ_FSTUB_VERIFY:-}" ] && repo_field="${repo_field},\"verify\":\"${AQ_FSTUB_VERIFY}\""
[ "${AQ_FSTUB_AUTOMERGE:-}" = "1" ] && repo_field="${repo_field},\"autoMerge\":true"
printf '{"claimed":true,"job":{"id":"%s","bodyMd":"%s","leaseEpoch":1%s},"lease":{"leaseEpoch":1}}\n200\n' \
"${AQ_FSTUB_JOB_ID:-fjob_1}" "${AQ_FSTUB_BODY:-do the work}" "$repo_field"
fi ;;
@ -877,23 +879,25 @@ printf '#!/usr/bin/env bash\necho "$@" >> "%s"\necho "https://github.com/test/re
export AGENT_QUEUE_ROOT="$tmp/queue-fl-pr"; export AQ_FLEET_CWD="$work"
"$AQ" init >/dev/null
export AQ_FSTUB_CALLS="$tmp/fl-pr-calls.log" AQ_FSTUB_CLAIM_FLAG="$tmp/fl-pr-claimed" \
AQ_FSTUB_JOB_ID="fjob_pr" AQ_FSTUB_BODY="add a file" AQ_FSTUB_REPO="$prbare" AQ_FSTUB_BASE="main"
AQ_FSTUB_JOB_ID="fjob_pr" AQ_FSTUB_BODY="add a file" AQ_FSTUB_REPO="$prbare" AQ_FSTUB_BASE="main" \
AQ_FSTUB_VERIFY="true" AQ_FSTUB_AUTOMERGE="1"
: > "$AQ_FSTUB_CALLS"; rm -f "$AQ_FSTUB_CLAIM_FLAG"
AQ_FLEET=1 AQ_FLEET_PR=1 AQ_FLEET_AUTOSHIP=1 AGENT_QUEUE_VERIFY=true AGENT_QUEUE_POLL=1 \
AQ_FLEET=1 AQ_FLEET_PR=1 AQ_FLEET_AUTOSHIP=1 AGENT_QUEUE_POLL=1 \
AQ_FLEET_REPOS_DIR="$tmp/pr-repos" GH_BIN="$ghstub" DEVIN_BIN="$prengine" "$AQ" run --once >/dev/null 2>&1
if git -C "$prbare" rev-parse --verify aq/job/fjob_pr >/dev/null 2>&1 \
&& grep -q -- '--title Add smoke marker file' "$tmp/gh-calls.log" 2>/dev/null \
&& grep -q 'pr merge' "$tmp/gh-calls.log" 2>/dev/null \
&& git -C "$prbare" ls-tree -r aq/job/fjob_pr --name-only 2>/dev/null | grep -qx 'PR_CHANGE.md' \
&& ! git -C "$prbare" ls-tree -r aq/job/fjob_pr --name-only 2>/dev/null | grep -qx '.aq_pr.md' \
&& grep -q '/fleet/jobs/fjob_pr/lease/release :: .*"prUrl":"https://github.com/test/repo/pull/7"' "$AQ_FSTUB_CALLS"; then
pass "fleet PR mode: agent-authored PR title used + .aq_pr.md not committed + prUrl reported"
pass "fleet PR mode: authored title + verify gate + auto-merge (gh pr merge) + prUrl reported"
else
echo "gh-calls: $(cat "$tmp/gh-calls.log" 2>/dev/null)" >&2
echo "tree: $(git -C "$prbare" ls-tree -r aq/job/fjob_pr --name-only 2>/dev/null | tr '\n' ' ')" >&2
grep release "$AQ_FSTUB_CALLS" >&2 2>/dev/null
fail "PR mode did not push branch / use authored title / report prUrl"
fail "PR mode did not push branch / authored title / auto-merge / report prUrl"
fi
unset AQ_FSTUB_REPO AQ_FSTUB_BASE
unset AQ_FSTUB_REPO AQ_FSTUB_BASE AQ_FSTUB_VERIFY AQ_FSTUB_AUTOMERGE
# 36. FENCING: PATCH returns conflict (stale epoch) → worker self-aborts, job is
# quarantined to failed/ (NOT review/testing/shipped), fenced is recorded.