eb01eb0172
Adds the operator surface for switching the controller pipeline between
the personal fork (drew/cleveragents-core) and the canonical repo
(cleveragents/cleveragents-core) via a MODE env + --prod CLI flag,
backed by safety primitives that make a wrong-mode launch loud rather
than silent.
run-controller-state-machine-pipeline.sh: --prod flag and MODE env
(primary home: .devcontainer/.env) select fork vs prod. After resolving
MODE, the launcher auto-sources the matching overlay file
(.devcontainer/.env.{fork,prod}) and asserts MODE didn't drift during
the source step. The drift assertion uses a readonly snapshot under an
obscure variable name so a stray ``MODE=fork`` in .env.prod aborts the
launch with a clear bash error rather than silently demoting the run.
CONTROLLER_RUN_DIR_ROOT now overrides the trial /tmp path so prod can
use a persistent /var/lib/cleveragents/run dir.
tools/launch_prod.sh (new): sibling to launch_fork.sh with the opposite
safety primitive — affirmative GET /repos/{owner}/{repo} that asserts
the target is non-fork, exists, isn't archived, and the bot has push.
On any failure, no env is exported. Honors HAL_* aliases for parity
with launch_fork.sh and prints a hard-to-miss PROD-MODE banner.
tools/controller/deploy/validate_db_mode.py (new): stamps a _mode_marker
table on each SQLite db (controller DB + telemetry cache) on first use,
asserts a match on every subsequent launch, and moves mismatched files
aside as <name>.<prior-mode>.bak.<ts> — never deletes. The --adopt flag
lets an operator grandfather in already-good pre-marker data without
losing history. Wired into the launcher's startup sequence before
OpenCode and the master start.
tools/_cache_path.py (new): single source of truth for the per-(owner,
repo) Forgejo cache file convention. .opencode/telemetry/server.py and
the launcher both delegate here so the dual-source-truth drift risk is
eliminated. tools/_pipeline_cache.py and tools/controller/db/models.py
documented as not owning the _mode_marker table so future migrations
leave it alone.
.opencode/telemetry/server.py: hosts the llm_activity scraper as a
background subprocess thread (60s cadence, --since-hours 1 in steady
state, full backfill on first tick). Re-homes the cost-telemetry data
path after the pr_state_warmer was retired by the controller migration
— without this the Cost tab freezes when the warmer's loop is gone.
Subprocess (not in-process) for isolation; failures swallowed.
opencode.json: local-claude provider's baseURL now reads
{env:LOCAL_PROXY_URL} instead of the literal http://127.0.0.1:3456/v1,
matching the apiKey pattern already in use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
474 lines
20 KiB
Bash
Executable File
474 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# tools/launch_fork.sh — Pin fork-mode env for the auto-agents pipeline.
|
|
#
|
|
# Why this exists
|
|
# ---------------
|
|
#
|
|
# The auto-agents pipeline has three complementary host-level entry
|
|
# points:
|
|
#
|
|
# 1. `scripts/dispatchers-launcher.sh` — runs the deterministic
|
|
# Python dispatchers (`tools/dispatch_review.py` and
|
|
# `tools/dispatch_implementer.py`) that own all reviewer /
|
|
# implementer orchestration. They read `FORGEJO_*` env vars
|
|
# directly and invoke the LLM workers (`pr-review-worker`,
|
|
# `implementation-worker`) per work item via OpenCode HTTP.
|
|
# These daemons replaced the legacy `pr-review-supervisor` /
|
|
# `implementation-supervisor` LLM agents that were
|
|
# decommissioned 2026-05-09.
|
|
#
|
|
# 2. `scripts/opencode-builder.sh` — boots the OpenCode server (or
|
|
# attaches to one already running on `127.0.0.1:4096`) and starts
|
|
# the `auto-agents` watchdog session. The watchdog reads
|
|
# dispatcher heartbeat files and logs WARNINGs; it does NOT
|
|
# restart anything. The OpenCode server it boots is also what
|
|
# the dispatchers above use for their HTTP worker invocations.
|
|
# Pass `OPENCODE_BUILDER_SERVER_ONLY=1` to skip the watchdog
|
|
# session and run server-only.
|
|
#
|
|
# 3. `tools/merge_drive.py` — the deterministic merge driver that
|
|
# enforces the train-build CI invariant. It reads `FORGEJO_OWNER`,
|
|
# `FORGEJO_REPO`, `FORGEJO_API_BASE`, and `GITEA_TOKEN` directly.
|
|
#
|
|
# Both entry points fall through to the canonical
|
|
# `cleveragents/cleveragents-core` when env vars aren't set, so a
|
|
# fork-mode test launched without a preamble will SILENTLY operate
|
|
# against the canonical repo. This script is the source-able preamble
|
|
# that pins everything to the operator's fork in one place.
|
|
#
|
|
# What gets exported (canonical names per auto-agents.md)
|
|
# -------------------------------------------------------
|
|
#
|
|
# Required by the auto-agents env contract:
|
|
# GIT_USER_NAME — git user.name for HAL9000's commits
|
|
# GIT_USER_EMAIL — git user.email for HAL9000's commits
|
|
# FORGEJO_PAT — HAL9000 PAT (LLM worker + driver identity)
|
|
# FORGEJO_USERNAME — HAL9000's login (resolved via whoami)
|
|
# FORGEJO_PASSWORD — HAL9000 web password (CI-log scraping)
|
|
# FORGEJO_REVIEWER_PAT — HAL9001 PAT (umbrella-PR auto-approver)
|
|
# FORGEJO_REVIEWER_USERNAME — HAL9001's login (resolved via whoami)
|
|
# FORGEJO_REVIEWER_PASSWORD — HAL9001 web password
|
|
#
|
|
# Driver-side compat:
|
|
# GITEA_TOKEN — synthesized from FORGEJO_PAT for the
|
|
# deterministic Python tools that still
|
|
# read this name (merge_drive.py,
|
|
# verify_invariant.py, audit_branch_protection.py,
|
|
# setup_*.py, etc.). They authenticate as
|
|
# HAL9000 — same identity as production runs.
|
|
# Pre-export GITEA_TOKEN before sourcing if
|
|
# you want to use a different identity (e.g.
|
|
# drew's admin PAT for one-time setup).
|
|
#
|
|
# Optional / driver-side:
|
|
# FORGEJO_URL — base URL (default: https://git.cleverthis.com)
|
|
# FORGEJO_OWNER — fork owner (default: drew)
|
|
# FORGEJO_REPO — fork repo (default: cleveragents-core)
|
|
# FORGEJO_API_BASE — derived from FORGEJO_URL
|
|
# FORGEJO_DEFAULT_BRANCH — default: master
|
|
# FORGEJO_ORG — org-label fallback only (default: cleveragents)
|
|
# CA_MAX_PARALLEL_WORKERS — historical knob; the deterministic
|
|
# dispatchers don't read it today, but
|
|
# the value is still re-exported so any
|
|
# future opt-in worker-pool implementation
|
|
# picks it up without operator changes.
|
|
# (Originally a supervisor-pool size for
|
|
# the now-decommissioned LLM supervisors.)
|
|
#
|
|
# Resolution priority for each name:
|
|
# 1. value already in the environment
|
|
# 2. canonical name in .devcontainer/.env or .env
|
|
# 3. HAL_* alias in .devcontainer/.env (PAT/password)
|
|
# 4. derive via API whoami (USERNAME only) or hard-coded default
|
|
#
|
|
# Usage
|
|
# -----
|
|
#
|
|
# source tools/launch_fork.sh
|
|
# tools/preflight_phase01.sh
|
|
# .venv/bin/python3 tools/merge_drive.py --status
|
|
# scripts/opencode-builder.sh
|
|
#
|
|
# # Override fork target:
|
|
# FORK_OWNER=otheruser FORK_REPO=myfork source tools/launch_fork.sh
|
|
#
|
|
# Safety guarantees
|
|
# -----------------
|
|
#
|
|
# - Validates the fork via a single read-only `GET /repos/<owner>/<repo>`
|
|
# (must exist, must be a fork, calling user must have push).
|
|
# - All resolved values stay in LOCAL variables until validation passes.
|
|
# On any failure, the parent shell's environment is left untouched —
|
|
# no FORGEJO_*, no GIT_*, no GITEA_TOKEN leakage.
|
|
# - A `printenv FORGEJO_PASSWORD | head -c 4` style probe in the parent
|
|
# shell is the operator's audit primitive; values are never echoed
|
|
# verbatim in the banner.
|
|
#
|
|
# Exit / return codes
|
|
# -------------------
|
|
#
|
|
# 0 — env exported, fork validated
|
|
# 1 — validation failed; nothing exported
|
|
# 2 — argument error / missing prerequisite
|
|
|
|
# Detect sourced vs executed once, up front. ${BASH_SOURCE[0]} == ${0}
|
|
# means "executed directly"; anything else means "sourced".
|
|
__lf_sourced=0
|
|
if [[ "${BASH_SOURCE[0]:-}" != "${0}" ]]; then
|
|
__lf_sourced=1
|
|
fi
|
|
|
|
# Wrap all logic in a function so we can `return` at any point without
|
|
# falling through into the export block. The function only exports
|
|
# anything on its successful path; failure paths print an error and
|
|
# return non-zero.
|
|
__lf_main() {
|
|
local script_path="${BASH_SOURCE[0]:-$0}"
|
|
local repo_root
|
|
repo_root="$(cd "$(dirname "$script_path")/.." && pwd)"
|
|
|
|
# ─── Defaults (overridable via FORK_* env vars) ───────────────────
|
|
local fork_owner="${FORK_OWNER:-drew}"
|
|
local fork_repo="${FORK_REPO:-cleveragents-core}"
|
|
local fork_url="${FORK_URL:-https://git.cleverthis.com}"
|
|
local fork_api_base="${FORK_API_BASE:-${fork_url%/}/api/v1}"
|
|
local fork_default_branch="${FORK_DEFAULT_BRANCH:-master}"
|
|
|
|
# ─── Step 0 — pick a python3 we can rely on ───────────────────────
|
|
local py
|
|
if [[ -x "$repo_root/.venv/bin/python3" ]]; then
|
|
py="$repo_root/.venv/bin/python3"
|
|
else
|
|
py="$(command -v python3 || true)"
|
|
fi
|
|
if [[ -z "$py" ]]; then
|
|
echo "ERROR: python3 not found (needed for fork validation and JSON parsing)" >&2
|
|
return 2
|
|
fi
|
|
|
|
# ─── Step 1 — small .env reader ───────────────────────────────────
|
|
# Reads KEY=value or KEY="value" from the fork-mode overlay file
|
|
# (`.devcontainer/.env.fork` preferred, repo-root `.env.fork` as
|
|
# fallback), then the shared env files. First match wins. Single-quoted
|
|
# values are also handled.
|
|
__lf_resolve_from_env() {
|
|
local key="$1"
|
|
local val=""
|
|
for f in \
|
|
"$repo_root/.devcontainer/.env.fork" \
|
|
"$repo_root/.env.fork" \
|
|
"$repo_root/.devcontainer/.env" \
|
|
"$repo_root/.env"; do
|
|
[[ -f "$f" ]] || continue
|
|
val="$(awk -v k="$key" -F= '
|
|
$0 ~ "^[[:space:]]*"k"[[:space:]]*=" {
|
|
# Strip key + leading whitespace and `=`
|
|
sub("^[[:space:]]*"k"[[:space:]]*=[[:space:]]*", "")
|
|
# Strip optional surrounding quotes
|
|
if (substr($0,1,1) == "\"" || substr($0,1,1) == "'\''") {
|
|
q = substr($0,1,1)
|
|
sub("^"q, "")
|
|
sub(q".*$", "")
|
|
} else {
|
|
# Unquoted: stop at first whitespace or `#`
|
|
sub(/[[:space:]#].*$/, "")
|
|
}
|
|
print
|
|
exit
|
|
}' "$f" 2>/dev/null || true)"
|
|
if [[ -n "$val" ]]; then
|
|
printf '%s' "$val"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Resolve <CANONICAL_NAME> with fallback to one or more aliases.
|
|
# Echoes the resolved value and returns 0; returns 1 if nothing found.
|
|
__lf_resolve_with_aliases() {
|
|
local canonical="$1"; shift
|
|
local existing="${!canonical:-}"
|
|
if [[ -n "$existing" ]]; then
|
|
printf '%s' "$existing"
|
|
return 0
|
|
fi
|
|
local v
|
|
if v="$(__lf_resolve_from_env "$canonical")"; then
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
local alias
|
|
for alias in "$@"; do
|
|
if v="$(__lf_resolve_from_env "$alias")"; then
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Helper: resolve a Forgejo login from a PAT via /api/v1/user.
|
|
__lf_whoami() {
|
|
local token="$1"
|
|
[[ -n "$token" ]] || return 1
|
|
curl -sf -H "Authorization: token $token" "$fork_api_base/user" 2>/dev/null \
|
|
| "$py" -c 'import json,sys
|
|
try:
|
|
print(json.load(sys.stdin).get("login") or "")
|
|
except Exception:
|
|
pass' 2>/dev/null || true
|
|
}
|
|
|
|
# ─── Step 2 — resolve every value into local variables ────────────
|
|
# Primary bot (HAL9000) PAT + password.
|
|
local resolved_pat
|
|
resolved_pat="$(__lf_resolve_with_aliases FORGEJO_PAT HAL_9000_API_KEY)" || resolved_pat=""
|
|
local resolved_pwd
|
|
resolved_pwd="$(__lf_resolve_with_aliases FORGEJO_PASSWORD HAL_9000_FORGEJO_PASSWORD)" || resolved_pwd=""
|
|
|
|
# GITEA_TOKEN is synthesized from FORGEJO_PAT (HAL9000) by default — that's
|
|
# the production-fidelity identity for the deterministic Python tools.
|
|
# Operators who want to use a different identity (e.g. drew's admin PAT
|
|
# for one-time fork setup) can pre-export GITEA_TOKEN before sourcing.
|
|
local resolved_gitea="${GITEA_TOKEN:-$resolved_pat}"
|
|
if [[ -z "$resolved_gitea" ]]; then
|
|
echo "ERROR: cannot resolve a token for GITEA_TOKEN — neither GITEA_TOKEN" >&2
|
|
echo " nor FORGEJO_PAT (or HAL_9000_API_KEY) is set." >&2
|
|
return 2
|
|
fi
|
|
|
|
# Reviewer bot (HAL9001) PAT + password.
|
|
local resolved_rev_pat
|
|
resolved_rev_pat="$(__lf_resolve_with_aliases FORGEJO_REVIEWER_PAT HAL_9001_API_KEY)" || resolved_rev_pat=""
|
|
local resolved_rev_pwd
|
|
resolved_rev_pwd="$(__lf_resolve_with_aliases FORGEJO_REVIEWER_PASSWORD HAL_9001_FORGEJO_PASSWORD)" || resolved_rev_pwd=""
|
|
|
|
# Git identity for HAL9000's commits. Defaults match Jeff's confirmed
|
|
# production env (`CleverThis` / `hal9000@cleverthis.com`) — also the
|
|
# author of every existing HAL9000 commit on the canonical repo.
|
|
local resolved_git_name
|
|
resolved_git_name="$(__lf_resolve_with_aliases GIT_USER_NAME)" || resolved_git_name="CleverThis"
|
|
local resolved_git_email
|
|
resolved_git_email="$(__lf_resolve_with_aliases GIT_USER_EMAIL)" || resolved_git_email="hal9000@cleverthis.com"
|
|
|
|
# CA_MAX_PARALLEL_WORKERS is optional; we pass it through if it exists.
|
|
local resolved_workers
|
|
resolved_workers="$(__lf_resolve_with_aliases CA_MAX_PARALLEL_WORKERS)" || resolved_workers=""
|
|
|
|
# FORGEJO_USERNAME / FORGEJO_REVIEWER_USERNAME come from .env or whoami.
|
|
local resolved_user
|
|
resolved_user="$(__lf_resolve_with_aliases FORGEJO_USERNAME)" || resolved_user=""
|
|
if [[ -z "$resolved_user" && -n "$resolved_pat" ]]; then
|
|
resolved_user="$(__lf_whoami "$resolved_pat")"
|
|
fi
|
|
local resolved_rev_user
|
|
resolved_rev_user="$(__lf_resolve_with_aliases FORGEJO_REVIEWER_USERNAME)" || resolved_rev_user=""
|
|
if [[ -z "$resolved_rev_user" && -n "$resolved_rev_pat" ]]; then
|
|
resolved_rev_user="$(__lf_whoami "$resolved_rev_pat")"
|
|
fi
|
|
|
|
# ─── Step 3 — required-var validation ─────────────────────────────
|
|
# The auto-agents agent will exit on startup if any of these are
|
|
# missing, so we surface that failure here instead of letting it crash
|
|
# 30 minutes into a run.
|
|
local -a missing=()
|
|
[[ -n "$resolved_pat" ]] || missing+=("FORGEJO_PAT (or HAL_9000_API_KEY)")
|
|
[[ -n "$resolved_pwd" ]] || missing+=("FORGEJO_PASSWORD (or HAL_9000_FORGEJO_PASSWORD)")
|
|
[[ -n "$resolved_user" ]] || missing+=("FORGEJO_USERNAME (no whoami match — PAT invalid?)")
|
|
[[ -n "$resolved_rev_pat" ]] || missing+=("FORGEJO_REVIEWER_PAT (or HAL_9001_API_KEY)")
|
|
[[ -n "$resolved_rev_pwd" ]] || missing+=("FORGEJO_REVIEWER_PASSWORD (or HAL_9001_FORGEJO_PASSWORD)")
|
|
[[ -n "$resolved_rev_user" ]] || missing+=("FORGEJO_REVIEWER_USERNAME (no whoami match — reviewer PAT invalid?)")
|
|
if (( ${#missing[@]} > 0 )); then
|
|
echo "ERROR: required env vars per .opencode/agents/auto-agents.md missing or unresolvable:" >&2
|
|
local m
|
|
for m in "${missing[@]}"; do
|
|
echo " - $m" >&2
|
|
done
|
|
echo "Refusing to launch fork-mode pipeline; nothing exported." >&2
|
|
return 1
|
|
fi
|
|
|
|
# ─── Step 4 — validate fork repo + driver permission ─────────────
|
|
local validation_json
|
|
if ! validation_json="$(
|
|
GITEA_TOKEN="$resolved_gitea" \
|
|
FORK_OWNER="$fork_owner" \
|
|
FORK_REPO="$fork_repo" \
|
|
FORK_API_BASE="$fork_api_base" \
|
|
"$py" - <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
token = os.environ["GITEA_TOKEN"]
|
|
owner = os.environ["FORK_OWNER"]
|
|
repo = os.environ["FORK_REPO"]
|
|
base = os.environ["FORK_API_BASE"].rstrip("/")
|
|
url = f"{base}/repos/{owner}/{repo}"
|
|
req = urllib.request.Request(
|
|
url,
|
|
headers={"Authorization": f"token {token}", "Accept": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
body = json.loads(resp.read())
|
|
except urllib.error.HTTPError as e:
|
|
msg = e.read().decode("utf-8", "replace")[:200]
|
|
print(f"ERROR: GET {url} returned HTTP {e.code}: {msg}", file=sys.stderr)
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"ERROR: GET {url} failed: {e!r}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
problems: list[str] = []
|
|
if not body.get("fork"):
|
|
problems.append(
|
|
f"target {owner}/{repo} is not a fork (fork={body.get('fork')!r}); "
|
|
"refusing to launch fork-mode pipeline against a non-fork."
|
|
)
|
|
parent_full = (body.get("parent") or {}).get("full_name")
|
|
perms = body.get("permissions") or {}
|
|
if not perms.get("push"):
|
|
problems.append(
|
|
f"calling user lacks push permission on {owner}/{repo} "
|
|
f"(permissions={perms!r}); driver cannot push train branches."
|
|
)
|
|
if problems:
|
|
for p in problems:
|
|
print(f"ERROR: {p}", file=sys.stderr)
|
|
sys.exit(1)
|
|
print(json.dumps({
|
|
"owner": owner,
|
|
"repo": repo,
|
|
"full_name": body.get("full_name"),
|
|
"parent_full_name": parent_full,
|
|
"default_branch": body.get("default_branch"),
|
|
}))
|
|
PY
|
|
)"; then
|
|
echo "ERROR: fork validation failed; nothing exported." >&2
|
|
return 1
|
|
fi
|
|
|
|
# ─── Step 5 — commit resolved values to the parent shell ─────────
|
|
# Driver-side
|
|
export GITEA_TOKEN="$resolved_gitea"
|
|
export FORGEJO_OWNER="$fork_owner"
|
|
export FORGEJO_REPO="$fork_repo"
|
|
export FORGEJO_URL="$fork_url"
|
|
export FORGEJO_API_BASE="$fork_api_base"
|
|
export FORGEJO_DEFAULT_BRANCH="$fork_default_branch"
|
|
export FORGEJO_ORG="${FORGEJO_ORG:-cleveragents}"
|
|
|
|
# Auto-agents env contract (8 required + 1 optional)
|
|
export GIT_USER_NAME="$resolved_git_name"
|
|
export GIT_USER_EMAIL="$resolved_git_email"
|
|
export FORGEJO_PAT="$resolved_pat"
|
|
export FORGEJO_USERNAME="$resolved_user"
|
|
export FORGEJO_PASSWORD="$resolved_pwd"
|
|
export FORGEJO_REVIEWER_PAT="$resolved_rev_pat"
|
|
export FORGEJO_REVIEWER_USERNAME="$resolved_rev_user"
|
|
export FORGEJO_REVIEWER_PASSWORD="$resolved_rev_pwd"
|
|
if [[ -n "$resolved_workers" ]]; then
|
|
export CA_MAX_PARALLEL_WORKERS="$resolved_workers"
|
|
fi
|
|
|
|
# G11 (2026-05-15): default the implementer estimator ON for fork-mode runs
|
|
# so the dispatcher exercises the new confidence-driven tier selection
|
|
# rather than the Tier-0 short-circuit. Operators can opt out for a single
|
|
# cycle by pre-exporting IMPLEMENTER_ESTIMATOR_ENABLED=0 before sourcing.
|
|
export IMPLEMENTER_ESTIMATOR_ENABLED="${IMPLEMENTER_ESTIMATOR_ENABLED:-1}"
|
|
|
|
# Merge driver is normally silent on idle cycles (only WARN/ERROR surface
|
|
# at default INFO level). Fork-mode runs are operator-driven debugging
|
|
# sessions where the cycle-by-cycle "what did the driver check / why did
|
|
# it skip this PR" trace is the load-bearing signal. Default to DEBUG so
|
|
# the loop-merge log is useful out of the box; pre-export
|
|
# MERGE_DRIVER_LOG_LEVEL=INFO before sourcing if you want production-style
|
|
# quiet output for a one-off test.
|
|
export MERGE_DRIVER_LOG_LEVEL="${MERGE_DRIVER_LOG_LEVEL:-DEBUG}"
|
|
|
|
# Phase 2 of the .drew/planning/fix list_prs_by_filter.md cutover
|
|
# (2026-05-16): reviewer dispatcher uses the Python-side delta-cached
|
|
# PR enumeration (`_pr_classification_cache.refresh_then_filter`)
|
|
# for the 5 work-group filters instead of subprocessing the
|
|
# `list_prs_*.ts` scripts. Eliminates the recurring 120s
|
|
# subprocess.TimeoutExpired on `list_prs_missing_ci_checks.ts`.
|
|
# Default-ON in fork-mode; pre-export `=0` for emergency rollback
|
|
# to the legacy TS-script path.
|
|
export REVIEW_DISPATCHER_USE_PYTHON_FILTERS="${REVIEW_DISPATCHER_USE_PYTHON_FILTERS:-1}"
|
|
|
|
# ─── Step 6 — banner ─────────────────────────────────────────────
|
|
local parent_full
|
|
parent_full="$(printf '%s' "$validation_json" | "$py" -c '
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
print(d.get("parent_full_name") or "(none)")')"
|
|
|
|
# Identity check on the synthesized GITEA_TOKEN (typically same as
|
|
# FORGEJO_PAT, but operators can override with an admin PAT).
|
|
local gitea_login
|
|
gitea_login="$(__lf_whoami "$resolved_gitea")"
|
|
local gitea_note=""
|
|
if [[ "$resolved_gitea" == "$resolved_pat" ]]; then
|
|
gitea_note="(== FORGEJO_PAT)"
|
|
else
|
|
gitea_note="(operator-overridden)"
|
|
fi
|
|
|
|
echo "=================================================================="
|
|
echo " Fork-mode env locked for the auto-agents pipeline"
|
|
echo " ----------------------------------------------------------------"
|
|
echo " Repo target"
|
|
echo " FORGEJO_OWNER = $FORGEJO_OWNER"
|
|
echo " FORGEJO_REPO = $FORGEJO_REPO"
|
|
echo " FORGEJO_URL = $FORGEJO_URL"
|
|
echo " FORGEJO_API_BASE = $FORGEJO_API_BASE"
|
|
echo " FORGEJO_DEFAULT_BRANCH = $FORGEJO_DEFAULT_BRANCH"
|
|
echo " FORGEJO_ORG = $FORGEJO_ORG (org-label fallback only)"
|
|
echo " Fork parent = $parent_full"
|
|
echo
|
|
echo " Identities (production-fidelity: HAL9000 + HAL9001 only)"
|
|
echo " FORGEJO_PAT -> ${FORGEJO_USERNAME} (driver + LLM workers, ${#FORGEJO_PAT} chars)"
|
|
echo " FORGEJO_PASSWORD = set (${#FORGEJO_PASSWORD} chars)"
|
|
echo " FORGEJO_REVIEWER_PAT -> ${FORGEJO_REVIEWER_USERNAME} (umbrella approver, ${#FORGEJO_REVIEWER_PAT} chars)"
|
|
echo " FORGEJO_REVIEWER_PASSWORD = set (${#FORGEJO_REVIEWER_PASSWORD} chars)"
|
|
echo " GITEA_TOKEN -> ${gitea_login:-<unknown>} $gitea_note"
|
|
echo
|
|
echo " Git author for HAL9000 commits"
|
|
echo " GIT_USER_NAME = $GIT_USER_NAME"
|
|
echo " GIT_USER_EMAIL = $GIT_USER_EMAIL"
|
|
if [[ -n "${CA_MAX_PARALLEL_WORKERS:-}" ]]; then
|
|
echo
|
|
echo " Parallelism"
|
|
echo " CA_MAX_PARALLEL_WORKERS = $CA_MAX_PARALLEL_WORKERS"
|
|
fi
|
|
echo "=================================================================="
|
|
return 0
|
|
}
|
|
|
|
__lf_main "$@"
|
|
__lf_rc=$?
|
|
|
|
if [[ $__lf_sourced -eq 0 && $__lf_rc -eq 0 ]]; then
|
|
echo
|
|
echo "WARNING: this script was EXECUTED, not sourced. The exports above"
|
|
echo "will not persist into your shell. Re-run as:"
|
|
echo
|
|
echo " source tools/launch_fork.sh"
|
|
echo
|
|
fi
|
|
|
|
# Bash treats `return` outside a function as an error when EXECUTED, so
|
|
# we have to branch on sourced-vs-executed here. When sourced we MUST
|
|
# `return` (an `exit` would terminate the parent shell). When executed
|
|
# we MUST `exit`.
|
|
if [[ $__lf_sourced -eq 1 ]]; then
|
|
return $__lf_rc
|
|
else
|
|
exit $__lf_rc
|
|
fi
|