Files
cleveragents-core/scripts/opencode-builder.sh
T
HAL9000 18d00c04c4
CI / lint (pull_request) Failing after 1m15s
CI / quality (pull_request) Successful in 1m21s
CI / typecheck (pull_request) Successful in 1m34s
CI / security (pull_request) Successful in 1m37s
CI / coverage (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 1m37s
CI / docker (pull_request) Has been skipped
CI / build (pull_request) Successful in 33s
CI / helm (pull_request) Successful in 26s
CI / push-validation (pull_request) Successful in 19s
CI / e2e_tests (pull_request) Successful in 3m20s
CI / integration_tests (pull_request) Successful in 4m32s
CI / status-check (pull_request) Failing after 3s
fix(skills): implement multi-scope agent skill discovery for global, project, and local tiers
Implements AgentSkillDiscovery class to support discovering Agent Skills from
multiple configured directories across three scopes (global, project, local).
Handles name collisions with precedence: local > project > global.

Adds comprehensive BDD test coverage for multi-scope discovery scenarios including:
- Global-only, project-only, and local-only discovery
- Combined discovery from all scopes
- Name collision resolution with proper precedence
- Non-existent and empty scope directory handling
- Multiple skills in same scope discovery

ISSUES CLOSED: #9369
2026-05-06 19:55:22 +00:00

173 lines
6.7 KiB
Bash
Executable File

#!/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
OWN_SERVER=false
# ── Helpers ───────────────────────────────────────────────────────────────
die() { printf "ERROR: %s\n" "$*" >&2; exit 1; }
log() { printf "[%s] %s\n" "$(date +%H:%M:%S)" "$*"; }
check_opencode_running() {
# Check if port is open and responding as OpenCode server
if curl -sf "${BASE}/global/health" >/dev/null 2>&1; then
return 0 # OpenCode server is running
else
return 1 # No OpenCode server detected
fi
}
cleanup() {
if $OWN_SERVER && [[ -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
elif ! $OWN_SERVER; then
log "Leaving existing OpenCode server running (not managed by this script)"
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 ─────────────────────────────────────────────
# Check if OpenCode server is already running
if check_opencode_running; then
echo "┌─────────────────────────────────────────────────────────────────┐"
echo "│ ⚠️ WARNING: OpenCode server already running on ${HOST}:${PORT}"
echo "│ 🔗 Connecting to existing server instead of starting new one"
echo "│ 💡 The script will NOT manage this server's lifecycle"
echo "└─────────────────────────────────────────────────────────────────┘"
echo ""
log "Using existing OpenCode server at ${HOST}:${PORT}"
OWN_SERVER=false
else
# Start our own server as before
log "Starting opencode server on ${HOST}:${PORT}..."
opencode serve --port "$PORT" --hostname "$HOST" &
SERVER_PID=$!
OWN_SERVER=true
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."
fi
# ── 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