build: runner for opencode project-builder
CI / benchmark-publish (push) Waiting to run
CI / lint (push) Successful in 34s
CI / quality (push) Successful in 41s
CI / typecheck (push) Successful in 48s
CI / security (push) Successful in 1m5s
CI / benchmark-regression (push) Waiting to run
CI / build (push) Successful in 24s
CI / helm (push) Successful in 22s
CI / unit_tests (push) Successful in 7m6s
CI / docker (push) Successful in 20s
CI / coverage (push) Successful in 10m0s
CI / e2e_tests (push) Successful in 16m13s
CI / integration_tests (push) Successful in 22m44s
CI / status-check (push) Successful in 1s
CI / benchmark-publish (push) Waiting to run
CI / lint (push) Successful in 34s
CI / quality (push) Successful in 41s
CI / typecheck (push) Successful in 48s
CI / security (push) Successful in 1m5s
CI / benchmark-regression (push) Waiting to run
CI / build (push) Successful in 24s
CI / helm (push) Successful in 22s
CI / unit_tests (push) Successful in 7m6s
CI / docker (push) Successful in 20s
CI / coverage (push) Successful in 10m0s
CI / e2e_tests (push) Successful in 16m13s
CI / integration_tests (push) Successful in 22m44s
CI / status-check (push) Successful in 1s
This commit is contained in:
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# opencode-builder.sh
|
||||
#
|
||||
# Launches an OpenCode server, creates a session with the product-builder
|
||||
# agent, and continuously sends "continue" messages in an infinite loop.
|
||||
#
|
||||
# Signal handling:
|
||||
# Ctrl+C once — finishes the in-flight request, then stops the loop
|
||||
# Ctrl+C twice — (within 2 seconds) kills the request and exits immediately
|
||||
#
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# ── Configuration (override via environment) ──────────────────────────────
|
||||
PORT="${OPENCODE_PORT:-4096}"
|
||||
HOST="${OPENCODE_HOST:-127.0.0.1}"
|
||||
BASE="http://${HOST}:${PORT}"
|
||||
AGENT="product-builder"
|
||||
PROMPT="Complete the current project's milestones up to and including v3.7.0 to a production ready state"
|
||||
HEALTH_TIMEOUT=60 # seconds to wait for the server to become healthy
|
||||
|
||||
# ── Internal state ────────────────────────────────────────────────────────
|
||||
SERVER_PID=""
|
||||
CURL_PID=""
|
||||
SESSION_ID=""
|
||||
STOP_LOOP=false
|
||||
LAST_SIGINT=0
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||
die() { printf "ERROR: %s\n" "$*" >&2; exit 1; }
|
||||
log() { printf "[%s] %s\n" "$(date +%H:%M:%S)" "$*"; }
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$SERVER_PID" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
log "Stopping opencode server (PID $SERVER_PID)..."
|
||||
kill "$SERVER_PID" 2>/dev/null
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
handle_sigint() {
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
# Second Ctrl+C within 2 seconds → force-quit
|
||||
if $STOP_LOOP && (( now - LAST_SIGINT <= 2 )); then
|
||||
printf "\nForce quit!\n"
|
||||
[[ -n "$CURL_PID" ]] && kill "$CURL_PID" 2>/dev/null || true
|
||||
cleanup
|
||||
exit 130
|
||||
fi
|
||||
|
||||
# First Ctrl+C → graceful stop after current request
|
||||
STOP_LOOP=true
|
||||
LAST_SIGINT=$now
|
||||
printf "\nWill stop after the current request completes. Ctrl+C again within 2s to force-quit.\n"
|
||||
}
|
||||
|
||||
trap handle_sigint INT
|
||||
trap cleanup EXIT
|
||||
|
||||
# ── Preflight checks ─────────────────────────────────────────────────────
|
||||
for cmd in curl jq opencode; do
|
||||
command -v "$cmd" &>/dev/null || die "'$cmd' is required but not found in PATH"
|
||||
done
|
||||
|
||||
# ── Start the opencode server ─────────────────────────────────────────────
|
||||
log "Starting opencode server on ${HOST}:${PORT}..."
|
||||
opencode serve --port "$PORT" --hostname "$HOST" &
|
||||
SERVER_PID=$!
|
||||
|
||||
log "Waiting for server to become healthy (up to ${HEALTH_TIMEOUT}s)..."
|
||||
healthy=false
|
||||
for (( i = 1; i <= HEALTH_TIMEOUT; i++ )); do
|
||||
if curl -sf "${BASE}/global/health" >/dev/null 2>&1; then
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
kill -0 "$SERVER_PID" 2>/dev/null || die "Server process exited unexpectedly"
|
||||
sleep 1
|
||||
done
|
||||
$healthy || die "Server did not become healthy within ${HEALTH_TIMEOUT}s"
|
||||
log "Server is ready."
|
||||
|
||||
# ── Create a session ──────────────────────────────────────────────────────
|
||||
log "Creating session..."
|
||||
SESSION_ID=$(
|
||||
curl -sf -X POST "${BASE}/session" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title":"product-builder-auto"}' \
|
||||
| jq -r '.id'
|
||||
) || die "Failed to create session"
|
||||
|
||||
[[ -n "$SESSION_ID" && "$SESSION_ID" != "null" ]] || die "Session ID is empty or null"
|
||||
log "Session ID: ${SESSION_ID}"
|
||||
|
||||
# ── send_message ──────────────────────────────────────────────────────────
|
||||
# Sends a JSON body to POST /session/:id/message and blocks until the LLM
|
||||
# responds. curl is run in a subprocess that ignores SIGINT (SIG_IGN is
|
||||
# inherited across exec), so a single Ctrl+C cannot kill the in-flight
|
||||
# request. The parent re-waits whenever its own trap handler interrupts
|
||||
# the `wait` builtin.
|
||||
send_message() {
|
||||
local body=$1
|
||||
|
||||
( trap '' INT
|
||||
exec curl -sf -X POST "${BASE}/session/${SESSION_ID}/message" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body" \
|
||||
-o /dev/null
|
||||
) &
|
||||
CURL_PID=$!
|
||||
|
||||
# Keep waiting until curl actually exits — `wait` can return early when
|
||||
# our SIGINT handler fires, so we loop on kill -0 to re-wait.
|
||||
while kill -0 "$CURL_PID" 2>/dev/null; do
|
||||
wait "$CURL_PID" 2>/dev/null || true
|
||||
done
|
||||
CURL_PID=""
|
||||
}
|
||||
|
||||
# ── Send the initial prompt ──────────────────────────────────────────────
|
||||
INIT_BODY=$(jq -nc --arg a "$AGENT" --arg t "$PROMPT" \
|
||||
'{agent: $a, parts: [{type: "text", text: $t}]}')
|
||||
|
||||
log "Sending initial prompt (agent: ${AGENT})..."
|
||||
log "Prompt: ${PROMPT}"
|
||||
echo ""
|
||||
send_message "$INIT_BODY"
|
||||
log "Initial prompt complete."
|
||||
|
||||
# ── Continue loop ─────────────────────────────────────────────────────────
|
||||
CONT_BODY=$(jq -nc '{parts: [{type: "text", text: "continue"}]}')
|
||||
N=0
|
||||
|
||||
while ! $STOP_LOOP; do
|
||||
N=$((N + 1))
|
||||
log "── continue #${N} ──"
|
||||
send_message "$CONT_BODY"
|
||||
log "continue #${N} complete."
|
||||
done
|
||||
|
||||
echo ""
|
||||
log "Loop stopped. Shutting down."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user