From 2a02a54b9db11da172dd7c6dba2df63f8252bd5b Mon Sep 17 00:00:00 2001 From: Sheldon Finlay Date: Fri, 27 Mar 2026 12:36:56 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20interactive=20mode=20=E2=80=94=20full?= =?UTF-8?q?=20CC=20sessions=20visible=20in=20tmux,=20headless=20mode=20via?= =?UTF-8?q?=20--headless=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- loop.sh | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/loop.sh b/loop.sh index 771b9a5..30faffb 100755 --- a/loop.sh +++ b/loop.sh @@ -122,6 +122,7 @@ while [[ $# -gt 0 ]]; do --tool=*) TOOL="${1#*=}"; shift ;; --no-hooks) AUTO_HOOKS=false; shift ;; --dry-run) DRY_RUN=true; shift ;; + --headless) export LOOP_HEADLESS=true; shift ;; --resume) RESUME=true; shift ;; --replan) log "ERROR: --replan is not yet implemented. Use /loop-plan interactively."; exit 1 ;; [0-9]*) MAX_ITERATIONS="$1"; shift ;; @@ -186,8 +187,13 @@ if [ -n "$BRANCH" ]; then fi # --- Agent runner --- -# Runs a prompt through the selected AI tool and captures output. -# Output is displayed live via tee to /dev/tty (if available) and captured to a temp file. +# Runs a prompt through the selected AI tool. +# 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. run_agent() { local prompt="$1" @@ -195,23 +201,29 @@ run_agent() { output_file=$(mktemp) 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 - if { true > /dev/tty; } 2>/dev/null; then + if [ "${LOOP_HEADLESS:-false}" != "true" ] && { true > /dev/tty; } 2>/dev/null; then has_tty=true fi # 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 ( case "$TOOL" in claude) if [ "$has_tty" = true ]; then - printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ - claude --dangerously-skip-permissions --output-format text \ - --print 2>&1 | tee /dev/tty > "$output_file" + # Interactive mode: full CC session visible in terminal. + # Use script to capture output while showing it live. + # 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 + # Headless mode: --print for autonomous operation printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ claude --dangerously-skip-permissions --output-format text \ --print 2>&1 > "$output_file" @@ -219,8 +231,8 @@ run_agent() { ;; amp) if [ "$has_tty" = true ]; then - printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ - amp --dangerously-allow-all 2>&1 | tee /dev/tty > "$output_file" + script -q "$output_file" \ + sh -c "printf '%s\n' \"\$(cat '$prompt_file')\" | amp --dangerously-allow-all" else printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ amp --dangerously-allow-all 2>&1 > "$output_file" @@ -233,11 +245,18 @@ run_agent() { esac ) || agent_exit=$? + rm -f "$prompt_file" + if [ "$agent_exit" -ne 0 ] && [ ! -s "$output_file" ]; then log "WARNING: Agent exited with code $agent_exit and produced no output." 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" LOOP_AGENT_TMPFILE="" }