feat: interactive mode — full CC sessions visible in tmux, headless mode via --headless flag

This commit is contained in:
2026-03-27 12:36:56 -04:00
parent a3cf3e7bae
commit 2a02a54b9d

41
loop.sh
View File

@@ -122,6 +122,7 @@ while [[ $# -gt 0 ]]; do
--tool=*) TOOL="${1#*=}"; shift ;; --tool=*) TOOL="${1#*=}"; shift ;;
--no-hooks) AUTO_HOOKS=false; shift ;; --no-hooks) AUTO_HOOKS=false; shift ;;
--dry-run) DRY_RUN=true; shift ;; --dry-run) DRY_RUN=true; shift ;;
--headless) export LOOP_HEADLESS=true; shift ;;
--resume) RESUME=true; shift ;; --resume) RESUME=true; shift ;;
--replan) log "ERROR: --replan is not yet implemented. Use /loop-plan interactively."; exit 1 ;; --replan) log "ERROR: --replan is not yet implemented. Use /loop-plan interactively."; exit 1 ;;
[0-9]*) MAX_ITERATIONS="$1"; shift ;; [0-9]*) MAX_ITERATIONS="$1"; shift ;;
@@ -186,8 +187,13 @@ if [ -n "$BRANCH" ]; then
fi fi
# --- Agent runner --- # --- Agent runner ---
# Runs a prompt through the selected AI tool and captures output. # Runs a prompt through the selected AI tool.
# Output is displayed live via tee to /dev/tty (if available) and captured to a temp file. # Two modes:
# Interactive (default when TTY available): runs claude in full interactive mode.
# The user sees the complete CC session (tool calls, file edits, etc.) in the terminal.
# Output is captured via `script` for verdict parsing.
# Headless (no TTY or LOOP_HEADLESS=true): uses claude --print for fully autonomous operation.
#
# The function prints the captured output to stdout for the caller to capture. # The function prints the captured output to stdout for the caller to capture.
run_agent() { run_agent() {
local prompt="$1" local prompt="$1"
@@ -195,23 +201,29 @@ run_agent() {
output_file=$(mktemp) output_file=$(mktemp)
LOOP_AGENT_TMPFILE="$output_file" # exposed for trap cleanup LOOP_AGENT_TMPFILE="$output_file" # exposed for trap cleanup
# Determine whether we can display live output local prompt_file
prompt_file=$(mktemp)
printf '%s\n' "$prompt" > "$prompt_file"
# Determine whether we can run interactively
local has_tty=false local has_tty=false
if { true > /dev/tty; } 2>/dev/null; then if [ "${LOOP_HEADLESS:-false}" != "true" ] && { true > /dev/tty; } 2>/dev/null; then
has_tty=true has_tty=true
fi fi
# Run in subshell so a non-zero exit from the AI tool doesn't kill the loop. # Run in subshell so a non-zero exit from the AI tool doesn't kill the loop.
# The subshell inherits set -e but its exit status is captured, not propagated.
local agent_exit=0 local agent_exit=0
( (
case "$TOOL" in case "$TOOL" in
claude) claude)
if [ "$has_tty" = true ]; then if [ "$has_tty" = true ]; then
printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ # Interactive mode: full CC session visible in terminal.
claude --dangerously-skip-permissions --output-format text \ # Use script to capture output while showing it live.
--print 2>&1 | tee /dev/tty > "$output_file" # The prompt is piped via stdin; claude reads it as the initial message.
script -q "$output_file" \
sh -c "printf '%s\n' \"\$(cat '$prompt_file')\" | claude --dangerously-skip-permissions"
else else
# Headless mode: --print for autonomous operation
printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \
claude --dangerously-skip-permissions --output-format text \ claude --dangerously-skip-permissions --output-format text \
--print 2>&1 > "$output_file" --print 2>&1 > "$output_file"
@@ -219,8 +231,8 @@ run_agent() {
;; ;;
amp) amp)
if [ "$has_tty" = true ]; then if [ "$has_tty" = true ]; then
printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ script -q "$output_file" \
amp --dangerously-allow-all 2>&1 | tee /dev/tty > "$output_file" sh -c "printf '%s\n' \"\$(cat '$prompt_file')\" | amp --dangerously-allow-all"
else else
printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \
amp --dangerously-allow-all 2>&1 > "$output_file" amp --dangerously-allow-all 2>&1 > "$output_file"
@@ -233,11 +245,18 @@ run_agent() {
esac esac
) || agent_exit=$? ) || agent_exit=$?
rm -f "$prompt_file"
if [ "$agent_exit" -ne 0 ] && [ ! -s "$output_file" ]; then if [ "$agent_exit" -ne 0 ] && [ ! -s "$output_file" ]; then
log "WARNING: Agent exited with code $agent_exit and produced no output." log "WARNING: Agent exited with code $agent_exit and produced no output."
fi fi
cat "$output_file" # Strip ANSI escape codes for clean verdict parsing
if command -v sed &>/dev/null; then
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' "$output_file"
else
cat "$output_file"
fi
rm -f "$output_file" rm -f "$output_file"
LOOP_AGENT_TMPFILE="" LOOP_AGENT_TMPFILE=""
} }