2026-03-19 07:25:25 +00:00
|
|
|
#!/usr/bin/env bash
|
2026-03-28 11:07:25 +00:00
|
|
|
# =============================================================================
|
|
|
|
|
# action-agent.sh — Synchronous action agent: SDK + shared libraries
|
|
|
|
|
#
|
|
|
|
|
# Synchronous bash loop using claude -p (one-shot invocation).
|
|
|
|
|
# No tmux sessions, no phase files — the bash script IS the state machine.
|
2026-03-19 07:25:25 +00:00
|
|
|
#
|
2026-03-19 22:16:01 +00:00
|
|
|
# Usage: ./action-agent.sh <issue-number> [project.toml]
|
2026-03-19 07:25:25 +00:00
|
|
|
#
|
2026-03-28 11:07:25 +00:00
|
|
|
# Flow:
|
|
|
|
|
# 1. Preflight: issue_check_deps(), memory guard, concurrency lock
|
|
|
|
|
# 2. Parse model from YAML front matter in issue body (custom model selection)
|
|
|
|
|
# 3. Worktree: worktree_create() for action isolation
|
|
|
|
|
# 4. Load formula from issue body
|
|
|
|
|
# 5. Build prompt: formula + prior non-bot comments (resume context)
|
|
|
|
|
# 6. agent_run(worktree, prompt) → Claude executes action, may push
|
|
|
|
|
# 7. If pushed: pr_walk_to_merge() from lib/pr-lifecycle.sh
|
|
|
|
|
# 8. Cleanup: worktree_cleanup(), issue_close()
|
2026-03-21 08:23:31 +00:00
|
|
|
#
|
2026-03-28 11:07:25 +00:00
|
|
|
# Action-specific (stays in runner):
|
|
|
|
|
# - YAML front matter parsing (model selection)
|
|
|
|
|
# - Bot username filtering for prior comments
|
|
|
|
|
# - Lifetime watchdog (MAX_LIFETIME=8h wall-clock cap)
|
|
|
|
|
# - Child process cleanup (docker compose, background jobs)
|
2026-03-19 07:25:25 +00:00
|
|
|
#
|
2026-03-28 11:07:25 +00:00
|
|
|
# From shared libraries:
|
|
|
|
|
# - Issue lifecycle: lib/issue-lifecycle.sh
|
|
|
|
|
# - Worktree: lib/worktree.sh
|
|
|
|
|
# - PR lifecycle: lib/pr-lifecycle.sh
|
|
|
|
|
# - Agent SDK: lib/agent-sdk.sh
|
|
|
|
|
#
|
|
|
|
|
# Log: action/action-poll-{project}.log
|
|
|
|
|
# =============================================================================
|
2026-03-19 07:25:25 +00:00
|
|
|
set -euo pipefail
|
|
|
|
|
|
2026-03-19 22:16:01 +00:00
|
|
|
ISSUE="${1:?Usage: action-agent.sh <issue-number> [project.toml]}"
|
|
|
|
|
export PROJECT_TOML="${2:-${PROJECT_TOML:-}}"
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
|
|
|
|
|
|
|
|
# shellcheck source=../lib/env.sh
|
|
|
|
|
source "$FACTORY_ROOT/lib/env.sh"
|
fix: Per-agent Forgejo accounts — identity and permissions via authorship (#747)
Each agent now gets its own Forgejo account (dev-bot, review-bot,
planner-bot, gardener-bot, vault-bot, supervisor-bot, predictor-bot,
action-bot) with a dedicated API token. This enables:
- Audit trail: every forge action attributable to a specific agent
- Permission boundaries: agents act under their own identity
- Vault authorization model: vault-bot comments = proof of approval
Changes:
- bin/disinto: setup_forge() creates all 8 bot accounts during init,
stores per-agent tokens (FORGE_*_TOKEN) in .env, adds all bots as
repo collaborators
- lib/env.sh: exports per-agent token vars with fallback to FORGE_TOKEN
for backwards compat; sets FORGE_BOT_USERNAMES default to all 8 bots
- Agent scripts: each agent overrides FORGE_TOKEN with its per-agent
token after sourcing env.sh (gardener, planner, supervisor, predictor,
vault, action)
- .env.example: documents all per-agent token fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:16:13 +00:00
|
|
|
# Use action-bot's own Forgejo identity (#747)
|
|
|
|
|
FORGE_TOKEN="${FORGE_ACTION_TOKEN:-${FORGE_TOKEN}}"
|
2026-03-28 11:07:25 +00:00
|
|
|
# shellcheck source=../lib/ci-helpers.sh
|
|
|
|
|
source "$FACTORY_ROOT/lib/ci-helpers.sh"
|
|
|
|
|
# shellcheck source=../lib/worktree.sh
|
|
|
|
|
source "$FACTORY_ROOT/lib/worktree.sh"
|
|
|
|
|
# shellcheck source=../lib/issue-lifecycle.sh
|
|
|
|
|
source "$FACTORY_ROOT/lib/issue-lifecycle.sh"
|
|
|
|
|
# shellcheck source=../lib/agent-sdk.sh
|
|
|
|
|
source "$FACTORY_ROOT/lib/agent-sdk.sh"
|
|
|
|
|
# shellcheck source=../lib/pr-lifecycle.sh
|
|
|
|
|
source "$FACTORY_ROOT/lib/pr-lifecycle.sh"
|
2026-03-19 07:25:25 +00:00
|
|
|
|
2026-03-20 17:39:44 +01:00
|
|
|
BRANCH="action/issue-${ISSUE}"
|
2026-03-21 08:23:31 +00:00
|
|
|
WORKTREE="/tmp/action-${ISSUE}-$(date +%s)"
|
2026-03-28 11:07:25 +00:00
|
|
|
LOCKFILE="/tmp/action-agent-${ISSUE}.lock"
|
|
|
|
|
LOGFILE="${DISINTO_LOG_DIR}/action/action-poll-${PROJECT_NAME:-default}.log"
|
|
|
|
|
# shellcheck disable=SC2034 # consumed by agent-sdk.sh
|
|
|
|
|
SID_FILE="/tmp/action-session-${PROJECT_NAME:-default}-${ISSUE}.sid"
|
|
|
|
|
MAX_LIFETIME="${ACTION_MAX_LIFETIME:-28800}" # 8h default wall-clock cap
|
|
|
|
|
SESSION_START_EPOCH=$(date +%s)
|
2026-03-20 17:39:44 +01:00
|
|
|
|
2026-03-19 07:25:25 +00:00
|
|
|
log() {
|
2026-03-20 17:39:44 +01:00
|
|
|
printf '[%s] action#%s %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$ISSUE" "$*" >> "$LOGFILE"
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 07:25:25 +00:00
|
|
|
# --- Concurrency lock (per issue) ---
|
|
|
|
|
if [ -f "$LOCKFILE" ]; then
|
|
|
|
|
LOCK_PID=$(cat "$LOCKFILE" 2>/dev/null || echo "")
|
|
|
|
|
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
|
|
|
|
|
log "SKIP: action-agent already running for #${ISSUE} (PID ${LOCK_PID})"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
rm -f "$LOCKFILE"
|
|
|
|
|
fi
|
|
|
|
|
echo $$ > "$LOCKFILE"
|
|
|
|
|
|
|
|
|
|
cleanup() {
|
2026-03-26 13:41:33 +00:00
|
|
|
local exit_code=$?
|
2026-03-20 23:51:46 +00:00
|
|
|
# Kill lifetime watchdog if running
|
|
|
|
|
if [ -n "${LIFETIME_WATCHDOG_PID:-}" ] && kill -0 "$LIFETIME_WATCHDOG_PID" 2>/dev/null; then
|
|
|
|
|
kill "$LIFETIME_WATCHDOG_PID" 2>/dev/null || true
|
|
|
|
|
wait "$LIFETIME_WATCHDOG_PID" 2>/dev/null || true
|
|
|
|
|
fi
|
2026-03-19 07:25:25 +00:00
|
|
|
rm -f "$LOCKFILE"
|
2026-03-21 08:23:31 +00:00
|
|
|
# Kill any remaining child processes spawned during the run
|
|
|
|
|
local children
|
|
|
|
|
children=$(jobs -p 2>/dev/null) || true
|
|
|
|
|
if [ -n "$children" ]; then
|
|
|
|
|
# shellcheck disable=SC2086 # intentional word splitting
|
|
|
|
|
kill $children 2>/dev/null || true
|
|
|
|
|
# shellcheck disable=SC2086
|
|
|
|
|
wait $children 2>/dev/null || true
|
|
|
|
|
fi
|
2026-03-20 17:39:44 +01:00
|
|
|
# Best-effort docker cleanup for containers started during this action
|
2026-03-21 08:23:31 +00:00
|
|
|
(cd "${WORKTREE}" 2>/dev/null && docker compose down 2>/dev/null) || true
|
2026-03-26 13:41:33 +00:00
|
|
|
# Preserve worktree on crash for debugging; clean up on success
|
2026-03-28 11:07:25 +00:00
|
|
|
if [ "$exit_code" -ne 0 ]; then
|
|
|
|
|
worktree_preserve "$WORKTREE" "crashed (exit=$exit_code)"
|
2026-03-26 13:41:33 +00:00
|
|
|
else
|
2026-03-28 11:07:25 +00:00
|
|
|
worktree_cleanup "$WORKTREE"
|
2026-03-26 13:41:33 +00:00
|
|
|
fi
|
2026-03-28 11:07:25 +00:00
|
|
|
rm -f "$SID_FILE"
|
2026-03-19 07:25:25 +00:00
|
|
|
}
|
|
|
|
|
trap cleanup EXIT
|
|
|
|
|
|
|
|
|
|
# --- Memory guard ---
|
2026-03-28 11:07:25 +00:00
|
|
|
memory_guard 2000
|
2026-03-19 07:25:25 +00:00
|
|
|
|
|
|
|
|
# --- Fetch issue ---
|
|
|
|
|
log "fetching issue #${ISSUE}"
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
ISSUE_JSON=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
|
|
|
"${FORGE_API}/issues/${ISSUE}") || true
|
2026-03-19 07:25:25 +00:00
|
|
|
|
|
|
|
|
if [ -z "$ISSUE_JSON" ] || ! printf '%s' "$ISSUE_JSON" | jq -e '.id' >/dev/null 2>&1; then
|
|
|
|
|
log "ERROR: failed to fetch issue #${ISSUE}"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
ISSUE_TITLE=$(printf '%s' "$ISSUE_JSON" | jq -r '.title')
|
|
|
|
|
ISSUE_BODY=$(printf '%s' "$ISSUE_JSON" | jq -r '.body // ""')
|
|
|
|
|
ISSUE_STATE=$(printf '%s' "$ISSUE_JSON" | jq -r '.state')
|
|
|
|
|
|
|
|
|
|
if [ "$ISSUE_STATE" != "open" ]; then
|
|
|
|
|
log "SKIP: issue #${ISSUE} is ${ISSUE_STATE}"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log "Issue: ${ISSUE_TITLE}"
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
# --- Dependency check (shared library) ---
|
|
|
|
|
if ! issue_check_deps "$ISSUE"; then
|
|
|
|
|
log "SKIP: issue #${ISSUE} blocked by: ${_ISSUE_BLOCKED_BY[*]}"
|
|
|
|
|
exit 0
|
2026-03-25 17:31:00 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-20 09:08:17 +00:00
|
|
|
# --- Extract model from YAML front matter (if present) ---
|
|
|
|
|
YAML_MODEL=$(printf '%s' "$ISSUE_BODY" | \
|
|
|
|
|
sed -n '/^---$/,/^---$/p' | grep '^model:' | awk '{print $2}' | tr -d '"' || true)
|
|
|
|
|
if [ -n "$YAML_MODEL" ]; then
|
|
|
|
|
export CLAUDE_MODEL="$YAML_MODEL"
|
|
|
|
|
log "model from front matter: ${YAML_MODEL}"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-20 19:01:56 +00:00
|
|
|
# --- Resolve bot username(s) for comment filtering ---
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
_bot_login=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
|
|
|
"${FORGE_API%%/repos*}/user" | jq -r '.login // empty' 2>/dev/null || true)
|
2026-03-20 19:01:56 +00:00
|
|
|
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
# Build list: token owner + any extra names from FORGE_BOT_USERNAMES (comma-separated)
|
2026-03-20 19:01:56 +00:00
|
|
|
_bot_logins="${_bot_login}"
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
if [ -n "${FORGE_BOT_USERNAMES:-}" ]; then
|
|
|
|
|
_bot_logins="${_bot_logins:+${_bot_logins},}${FORGE_BOT_USERNAMES}"
|
2026-03-20 19:01:56 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# --- Fetch existing comments (resume context, excluding bot comments) ---
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
COMMENTS_JSON=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
|
|
|
"${FORGE_API}/issues/${ISSUE}/comments?limit=50") || true
|
2026-03-19 07:25:25 +00:00
|
|
|
|
|
|
|
|
PRIOR_COMMENTS=""
|
|
|
|
|
if [ -n "$COMMENTS_JSON" ] && [ "$COMMENTS_JSON" != "null" ] && [ "$COMMENTS_JSON" != "[]" ]; then
|
|
|
|
|
PRIOR_COMMENTS=$(printf '%s' "$COMMENTS_JSON" | \
|
2026-03-20 19:01:56 +00:00
|
|
|
jq -r --arg bots "$_bot_logins" \
|
|
|
|
|
'($bots | split(",") | map(select(. != ""))) as $bl |
|
|
|
|
|
.[] | select(.user.login as $u | $bl | index($u) | not) |
|
|
|
|
|
"[\(.user.login) at \(.created_at[:19])]\n\(.body)\n---"' 2>/dev/null || true)
|
2026-03-19 07:25:25 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
# --- Determine git remote ---
|
2026-03-21 08:23:31 +00:00
|
|
|
cd "${PROJECT_REPO_ROOT}"
|
2026-03-25 06:17:34 +00:00
|
|
|
_forge_host=$(echo "$FORGE_URL" | sed 's|https\?://||; s|/.*||')
|
|
|
|
|
FORGE_REMOTE=$(git remote -v | awk -v host="$_forge_host" '$2 ~ host && /\(push\)/ {print $1; exit}')
|
|
|
|
|
FORGE_REMOTE="${FORGE_REMOTE:-origin}"
|
|
|
|
|
export FORGE_REMOTE
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
# --- Create isolated worktree ---
|
|
|
|
|
log "creating worktree: ${WORKTREE}"
|
2026-03-25 06:17:34 +00:00
|
|
|
git fetch "${FORGE_REMOTE}" "${PRIMARY_BRANCH}" 2>/dev/null || true
|
2026-03-28 11:07:25 +00:00
|
|
|
if ! worktree_create "$WORKTREE" "$BRANCH"; then
|
2026-03-21 08:23:31 +00:00
|
|
|
log "ERROR: worktree creation failed"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
log "worktree ready: ${WORKTREE}"
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
# --- Build prompt ---
|
2026-03-19 07:25:25 +00:00
|
|
|
PRIOR_SECTION=""
|
|
|
|
|
if [ -n "$PRIOR_COMMENTS" ]; then
|
|
|
|
|
PRIOR_SECTION="## Prior comments (resume context)
|
|
|
|
|
|
|
|
|
|
${PRIOR_COMMENTS}
|
|
|
|
|
|
|
|
|
|
"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
GIT_INSTRUCTIONS=$(build_phase_protocol_prompt "$BRANCH" "$FORGE_REMOTE")
|
2026-03-20 17:39:44 +01:00
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
PROMPT="You are an action agent. Your job is to execute the action formula
|
2026-03-20 17:39:44 +01:00
|
|
|
in the issue below.
|
2026-03-19 07:25:25 +00:00
|
|
|
|
|
|
|
|
## Issue #${ISSUE}: ${ISSUE_TITLE}
|
|
|
|
|
|
|
|
|
|
${ISSUE_BODY}
|
2026-03-28 11:07:25 +00:00
|
|
|
|
2026-03-19 07:25:25 +00:00
|
|
|
${PRIOR_SECTION}## Instructions
|
|
|
|
|
|
|
|
|
|
1. Read the action formula steps in the issue body carefully.
|
|
|
|
|
|
|
|
|
|
2. Execute each step in order using your Bash tool and any other tools available.
|
|
|
|
|
|
|
|
|
|
3. Post progress as comments on issue #${ISSUE} after significant steps:
|
|
|
|
|
curl -sf -X POST \\
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
-H \"Authorization: token \${FORGE_TOKEN}\" \\
|
2026-03-19 07:25:25 +00:00
|
|
|
-H 'Content-Type: application/json' \\
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
\"${FORGE_API}/issues/${ISSUE}/comments\" \\
|
2026-03-19 07:25:25 +00:00
|
|
|
-d \"{\\\"body\\\": \\\"your comment here\\\"}\"
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
4. If a step requires human input or approval, post a comment explaining what
|
|
|
|
|
is needed and stop — the orchestrator will block the issue.
|
2026-03-19 07:25:25 +00:00
|
|
|
|
2026-03-20 17:39:44 +01:00
|
|
|
### Path A: If this action produces code changes (e.g. config updates, baselines):
|
2026-03-21 08:23:31 +00:00
|
|
|
- You are already in an isolated worktree at: ${WORKTREE}
|
2026-03-28 11:07:25 +00:00
|
|
|
- You are on branch: ${BRANCH}
|
2026-03-25 06:17:34 +00:00
|
|
|
- Make your changes, commit, and push: git push ${FORGE_REMOTE} ${BRANCH}
|
2026-03-21 08:23:31 +00:00
|
|
|
- **IMPORTANT:** The worktree is destroyed after completion. Push all
|
2026-03-28 11:07:25 +00:00
|
|
|
results before finishing — unpushed work will be lost.
|
2026-03-20 17:39:44 +01:00
|
|
|
|
|
|
|
|
### Path B: If this action produces no code changes (investigation, report):
|
|
|
|
|
- Post results as a comment on issue #${ISSUE}.
|
2026-03-21 08:23:31 +00:00
|
|
|
- **IMPORTANT:** The worktree is destroyed after completion. Copy any
|
2026-03-28 11:07:25 +00:00
|
|
|
files you need to persistent paths before finishing.
|
2026-03-20 17:39:44 +01:00
|
|
|
|
|
|
|
|
5. Environment variables available in your bash sessions:
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
FORGE_TOKEN, FORGE_API, FORGE_REPO, FORGE_WEB, PROJECT_NAME
|
2026-03-19 07:25:25 +00:00
|
|
|
(all sourced from ${FACTORY_ROOT}/.env)
|
|
|
|
|
|
2026-03-21 09:54:21 +00:00
|
|
|
### CRITICAL: Never embed secrets in issue bodies, comments, or PR descriptions
|
|
|
|
|
- NEVER put API keys, tokens, passwords, or private keys in issue text or comments.
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
- Always reference secrets via env var names (e.g. \\\$BASE_RPC_URL, \\\${FORGE_TOKEN}).
|
2026-03-21 09:54:21 +00:00
|
|
|
- If a formula step needs a secret, read it from .env or the environment at runtime.
|
|
|
|
|
- Before posting any comment, verify it contains no credentials, hex keys > 32 chars,
|
|
|
|
|
or URLs with embedded API keys.
|
|
|
|
|
|
2026-03-20 17:39:44 +01:00
|
|
|
If the prior comments above show work already completed, resume from where it
|
|
|
|
|
left off.
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
${GIT_INSTRUCTIONS}"
|
2026-03-19 07:25:25 +00:00
|
|
|
|
2026-03-20 23:51:46 +00:00
|
|
|
# --- Wall-clock lifetime watchdog (background) ---
|
2026-03-28 11:07:25 +00:00
|
|
|
# Caps total run time independently of claude -p timeout. When the cap is
|
|
|
|
|
# hit the watchdog kills the main process, which triggers cleanup via trap.
|
2026-03-20 23:51:46 +00:00
|
|
|
_lifetime_watchdog() {
|
|
|
|
|
local remaining=$(( MAX_LIFETIME - ($(date +%s) - SESSION_START_EPOCH) ))
|
|
|
|
|
[ "$remaining" -le 0 ] && remaining=1
|
|
|
|
|
sleep "$remaining"
|
|
|
|
|
local hours=$(( MAX_LIFETIME / 3600 ))
|
2026-03-28 11:07:25 +00:00
|
|
|
log "MAX_LIFETIME (${hours}h) reached — killing agent"
|
2026-03-20 23:51:46 +00:00
|
|
|
# Post summary comment on issue
|
2026-03-28 11:07:25 +00:00
|
|
|
local body="Action agent killed: wall-clock lifetime cap (${hours}h) reached."
|
2026-03-20 23:51:46 +00:00
|
|
|
curl -sf -X POST \
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
-H "Authorization: token ${FORGE_TOKEN}" \
|
2026-03-20 23:51:46 +00:00
|
|
|
-H 'Content-Type: application/json' \
|
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
|
|
|
"${FORGE_API}/issues/${ISSUE}/comments" \
|
2026-03-20 23:51:46 +00:00
|
|
|
-d "{\"body\": \"${body}\"}" >/dev/null 2>&1 || true
|
2026-03-28 11:07:25 +00:00
|
|
|
kill $$ 2>/dev/null || true
|
2026-03-20 23:51:46 +00:00
|
|
|
}
|
|
|
|
|
_lifetime_watchdog &
|
|
|
|
|
LIFETIME_WATCHDOG_PID=$!
|
|
|
|
|
|
2026-03-28 11:07:25 +00:00
|
|
|
# --- Run agent ---
|
|
|
|
|
log "running agent (worktree: ${WORKTREE})"
|
|
|
|
|
agent_run --worktree "$WORKTREE" "$PROMPT"
|
|
|
|
|
log "agent_run complete"
|
|
|
|
|
|
|
|
|
|
# --- Detect if branch was pushed (Path A vs Path B) ---
|
|
|
|
|
PUSHED=false
|
|
|
|
|
# Check if remote branch exists
|
|
|
|
|
git fetch "${FORGE_REMOTE}" "$BRANCH" 2>/dev/null || true
|
|
|
|
|
if git rev-parse --verify "${FORGE_REMOTE}/${BRANCH}" >/dev/null 2>&1; then
|
|
|
|
|
PUSHED=true
|
|
|
|
|
fi
|
|
|
|
|
# Fallback: check local commits ahead of base
|
|
|
|
|
if [ "$PUSHED" = false ]; then
|
|
|
|
|
if git -C "$WORKTREE" log "${FORGE_REMOTE}/${PRIMARY_BRANCH}..${BRANCH}" --oneline 2>/dev/null | grep -q .; then
|
|
|
|
|
PUSHED=true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$PUSHED" = true ]; then
|
|
|
|
|
# --- Path A: code changes pushed — create PR and walk to merge ---
|
|
|
|
|
log "branch pushed — creating PR"
|
|
|
|
|
PR_NUMBER=""
|
|
|
|
|
PR_NUMBER=$(pr_create "$BRANCH" "action: ${ISSUE_TITLE}" \
|
|
|
|
|
"Closes #${ISSUE}
|
|
|
|
|
|
|
|
|
|
Automated action execution by action-agent.") || true
|
|
|
|
|
|
|
|
|
|
if [ -n "$PR_NUMBER" ]; then
|
|
|
|
|
log "walking PR #${PR_NUMBER} to merge"
|
|
|
|
|
pr_walk_to_merge "$PR_NUMBER" "$_AGENT_SESSION_ID" "$WORKTREE" || true
|
|
|
|
|
|
|
|
|
|
case "${_PR_WALK_EXIT_REASON:-}" in
|
|
|
|
|
merged)
|
|
|
|
|
log "PR #${PR_NUMBER} merged — closing issue"
|
|
|
|
|
issue_close "$ISSUE"
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
log "PR #${PR_NUMBER} not merged (reason: ${_PR_WALK_EXIT_REASON:-unknown})"
|
|
|
|
|
issue_block "$ISSUE" "pr_not_merged: ${_PR_WALK_EXIT_REASON:-unknown}"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
else
|
|
|
|
|
log "ERROR: failed to create PR"
|
|
|
|
|
issue_block "$ISSUE" "pr_creation_failed"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
# --- Path B: no code changes — close issue directly ---
|
|
|
|
|
log "no branch pushed — closing issue (Path B)"
|
|
|
|
|
issue_close "$ISSUE"
|
|
|
|
|
fi
|
2026-03-19 07:25:25 +00:00
|
|
|
|
|
|
|
|
log "action-agent finished for issue #${ISSUE}"
|