#!/usr/bin/env bash # mav-agent — spawn a persistent sub-agent (claude or codex) in a named tmux window. # each agent lives in its own tmux window inside the "mav" session. # # usage: # mav-agent run spawn a claude sub-agent with task # mav-agent run --codex spawn a codex sub-agent with task # mav-agent read capture current output from agent window # mav-agent status running | done | none (reliable, marker-based) # mav-agent wait [timeout_s] block until agent done (marker-based) # mav-agent result print the agent's final stdout (after done) # mav-agent list list all agent windows + status # mav-agent kill kill an agent window # # agents run in mav tmux session, window named . # clean handoff: each run writes /tmp/mav-agent-.done when the CLI exits, # and streams full stdout to /tmp/mav-agent-.out. status/wait poll the marker # (no flaky prompt-sniffing), so the main actor always knows when work is finished. SESSION="mav" RUNDIR="/tmp/mav-agent" mkdir -p "$RUNDIR" 2>/dev/null CLAUDE=/Users/m1/.local/bin/claude OR_KEY=$(python3 -c "import json; print(json.load(open('/Users/m1/.config/mav/bridge/config.json')).get('openrouterKey',''))" 2>/dev/null) # ensure session exists TMUX= tmux has-session -t "$SESSION" 2>/dev/null || TMUX= tmux new-session -d -s "$SESSION" window_exists() { TMUX= tmux list-windows -t "$SESSION" -F "#{window_name}" 2>/dev/null | grep -q "^${1}$"; } case "${1:-list}" in run) NAME="${2:?usage: mav-agent run [--codex] }" shift 2 # detect --codex flag CLI="claude" if [[ "${1:-}" == "--codex" ]]; then CLI="codex"; shift; fi TASK="${*:?task required}" # kill existing window with same name if present window_exists "$NAME" && TMUX= tmux kill-window -t "${SESSION}:${NAME}" 2>/dev/null OUT="$RUNDIR/${NAME}.out" DONE="$RUNDIR/${NAME}.done" rm -f "$OUT" "$DONE" 2>/dev/null # create new window at the next free index (avoids "index N in use" on gaps) NEXT=$(( $(TMUX= tmux list-windows -t "$SESSION" -F "#{window_index}" 2>/dev/null | sort -n | tail -1) + 1 )) TMUX= tmux new-window -d -t "${SESSION}:${NEXT}" -n "$NAME" if [[ "$CLI" == "codex" ]]; then CLI_CMD="codex --approval-mode auto-edit -q $(printf '%q' "$TASK")" else CLI_CMD="$CLAUDE -p $(printf '%q' "$TASK") --output-format text --permission-mode bypassPermissions --model opus" fi # stream stdout to a file, then drop a done-marker with the exit code. # this is the clean handoff: marker => work finished, no prompt-sniffing. # run via bash -c so PIPESTATUS works regardless of the pane's login shell. INNER="{ $CLI_CMD ; } 2>&1 | tee $(printf '%q' "$OUT"); echo \${PIPESTATUS[0]} > $(printf '%q' "$DONE")" CMD="bash -c $(printf '%q' "$INNER")" TMUX= tmux send-keys -t "${SESSION}:${NAME}" "$CMD" Enter echo "[mav-agent] started: $CLI agent in window '${SESSION}:${NAME}'" echo "task: $TASK" echo "marker: $DONE | output: $OUT" ;; status) NAME="${2:?usage: mav-agent status }" if [[ -f "$RUNDIR/${NAME}.done" ]]; then echo "done (exit $(cat "$RUNDIR/${NAME}.done" 2>/dev/null | tr -d '[:space:]'))"; elif window_exists "$NAME"; then echo "running"; else echo "none"; fi ;; result) NAME="${2:?usage: mav-agent result }" if [[ -f "$RUNDIR/${NAME}.out" ]]; then cat "$RUNDIR/${NAME}.out"; else echo "no output for: $NAME"; exit 1; fi ;; read) NAME="${2:?usage: mav-agent read }" if ! window_exists "$NAME"; then echo "no agent window: $NAME"; exit 1; fi TMUX= tmux capture-pane -t "${SESSION}:${NAME}" -p 2>/dev/null ;; wait) NAME="${2:?usage: mav-agent wait [timeout_s]}" TIMEOUT="${3:-120}" DONE="$RUNDIR/${NAME}.done" if [[ ! -f "$DONE" ]] && ! window_exists "$NAME"; then echo "no agent: $NAME"; exit 1; fi echo "waiting for $NAME to finish (${TIMEOUT}s max)..." for i in $(seq 1 $((TIMEOUT * 2))); do if [[ -f "$DONE" ]]; then echo "[done] exit $(cat "$DONE" 2>/dev/null | tr -d '[:space:]')" tail -20 "$RUNDIR/${NAME}.out" 2>/dev/null exit 0 fi sleep 0.5 done echo "[timeout] agent still running after ${TIMEOUT}s" tail -10 "$RUNDIR/${NAME}.out" 2>/dev/null exit 2 ;; list) echo "=== agent windows in session '$SESSION' ===" TMUX= tmux list-windows -t "$SESSION" -F "#{window_index}: #{window_name}" 2>/dev/null | while read -r line; do wn="${line#*: }" if [[ -f "$RUNDIR/${wn}.done" ]]; then st="done(exit $(cat "$RUNDIR/${wn}.done" 2>/dev/null | tr -d '[:space:]'))"; else st="running"; fi echo " $line [$st]" done [[ -z "$(TMUX= tmux list-windows -t "$SESSION" 2>/dev/null)" ]] && echo "(no session)" ;; kill) NAME="${2:?usage: mav-agent kill }" TMUX= tmux kill-window -t "${SESSION}:${NAME}" 2>/dev/null && echo "killed: $NAME" || echo "not found: $NAME" ;; *) echo "usage: mav-agent run [--codex] | read | status | wait [s] | result | list | kill " exit 1 ;; esac