#!/bin/bash # subagent-tab — Subagenten als Panes (tiled) in einem xterm auf dem Arzopa-Monitor # # Nutzung: # subagent-tab init — Session + xterm auf Arzopa starten # subagent-tab spawn [aufgabe] — Neuen Pane mit pi # subagent-tab list — Alle Panes anzeigen # subagent-tab kill — Pane schliessen # subagent-tab killall — Alles schliessen # # Im Pane: # Ctrl+B Pfeiltasten — Zwischen Panes wechseln # Ctrl+B z — Pane Vollbild (nochmal zurück) # Ctrl+B d — Detach set -euo pipefail SESSION="agents" # --- Monitor-Erkennung --- detect_monitor() { # Manuelle Override if [ -n "${AGENTS_MONITOR:-}" ]; then echo "$AGENTS_MONITOR" return fi # Auto: erster nicht-primary Monitor xrandr --query 2>/dev/null | grep " connected" | grep -v "primary" | head -1 | awk '{print $1}' } get_monitor_geometry() { local MONITOR="${1:-$(detect_monitor)}" local LINE=$(xrandr --query 2>/dev/null | grep "$MONITOR" | head -1) # Auflösung und Position extrahieren: z.B. "1920x1080+0+1080" local GEO=$(echo "$LINE" | grep -oE '[0-9]+x[0-9]+\+[0-9]+\+[0-9]+' | head -1) echo "$GEO" } # --- Farben --- C_RESET="\033[0m" C_GREEN="\033[32m" C_CYAN="\033[36m" C_YELLOW="\033[33m" C_RED="\033[31m" C_DIM="\033[2m" usage() { cat < [aufgabe] Neuen Pane mit pi subagent-tab spawn --provider P --model M [aufgabe] subagent-tab list Alle Panes anzeigen subagent-tab kill Pane schliessen subagent-tab killall Alles schliessen Steuerung: Ctrl+B Pfeiltasten Zwischen Panes wechseln Ctrl+B z Pane Vollbild (nochmal zurück) Ctrl+B d Detach (läuft weiter) Monitor: Automatisch: erster nicht-primary Monitor (Arzopa) Manuell: AGENTS_MONITOR=DP-3 subagent-tab init EOF } # --- Init --- cmd_init() { if tmux has-session -t "$SESSION" 2>/dev/null; then echo -e "${C_GREEN}Session '$SESSION' existiert bereits.${C_RESET}" return 0 fi local MONITOR=$(detect_monitor) if [ -z "$MONITOR" ]; then echo -e "${C_YELLOW}Kein zweiter Monitor gefunden. Starte im aktuellen Terminal.${C_RESET}" tmux new-session -d -s "$SESSION" -x 220 -y 50 else local GEO=$(get_monitor_geometry "$MONITOR") local WIDTH=$(echo "$GEO" | grep -oE '^[0-9]+') local HEIGHT=$(echo "$GEO" | grep -oE 'x[0-9]+' | tr -d 'x') local POS_X=$(echo "$GEO" | grep -oE '\+[0-9]+' | head -1 | tr -d '+') local POS_Y=$(echo "$GEO" | grep -oE '\+[0-9]+$' | tr -d '+') # Geometrie für xterm berechnen (Font ~8x16 bei 1920x1080 ≈ 240x67) local COLS=$((WIDTH / 8)) local ROWS=$((HEIGHT / 16)) # tmux Session starten tmux new-session -d -s "$SESSION" -x "$COLS" -y "$ROWS" # xterm auf dem Arzopa-Monitor positionieren xterm -geometry "${COLS}x${ROWS}+${POS_X}+${POS_Y}" \ -fa 'Monospace' -fs 10 \ -title "⚡ Agents" \ -e "tmux attach -t $SESSION" & local XTERM_PID=$! echo -e "${C_GREEN}xterm auf $MONITOR gestartet (PID $XTERM_PID).${C_RESET}" echo -e " Geometrie: ${COLS}x${ROWS} an Position +${POS_X}+${POS_Y}" fi # tmux Styling tmux set-option -t "$SESSION" status-position top tmux set-option -t "$SESSION" status-style "bg=#1a1b26,fg=#a9b1d6" tmux set-option -t "$SESSION" status-left-length 30 tmux set-option -t "$SESSION" status-left " #[bold]⚡ Agents " tmux set-option -t "$SESSION" status-right " %H:%M " tmux set-option -t "$SESSION" mouse on tmux set-option -t "$SESSION" set-clipboard on # Tiled-Layout Hooks tmux set-hook -t "$SESSION" after-split-window 'select-layout tiled' tmux set-hook -t "$SESSION" pane-exited 'select-layout tiled' sleep 1 echo -e "Neuer Pane: ${C_CYAN}subagent-tab spawn ${C_RESET}" } # --- Spawn --- cmd_spawn() { local PROVIDER="" local MODEL="" while [ $# -gt 0 ]; do case "$1" in --provider) PROVIDER="$2"; shift 2 ;; --model) MODEL="$2"; shift 2 ;; --) shift; break ;; -*) echo "Unbekannt: $1"; exit 1 ;; *) break ;; esac done local NAME="${1:-}" local TASK="${2:-}" if [ -z "$NAME" ]; then echo "Fehler: Name erforderlich." exit 1 fi # Session muss existieren cmd_init local SLUG=$(echo "$NAME" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-') local WORKDIR="/tmp/subagent-${SLUG}" mkdir -p "$WORKDIR" # Pi-Befehl bauen local PI_CMD="pi" [ -n "$PROVIDER" ] && PI_CMD="$PI_CMD --provider $PROVIDER" [ -n "$MODEL" ] && PI_CMD="$PI_CMD --model $MODEL" # Pane erstellen local PANE_COUNT=$(tmux list-panes -t "$SESSION:0" 2>/dev/null | wc -l) if [ "$PANE_COUNT" -eq 1 ]; then # Prüfen ob das erste Pane leer ist (kein Prozess) local CONTENT=$(tmux capture-pane -t "$SESSION:0.0" -p 2>/dev/null | tail -3) if [ -z "$(echo "$CONTENT" | tr -d '[:space:]')" ] || echo "$CONTENT" | grep -qE '^\$'; then # Leer → Pi hier starten tmux send-keys -t "$SESSION:0.0" "cd $WORKDIR && $PI_CMD" Enter tmux send-keys -t "$SESSION:0.0"" ; echo '=== Pi beendet ==='; read" 2>/dev/null || true else # Belegt → split tmux split-window -t "$SESSION:0" -c "$WORKDIR" "$PI_CMD; echo '=== Pi beendet ==='; read" fi else # Bereits mehrere Panes → split tmux split-window -t "$SESSION:0" -c "$WORKDIR" "$PI_CMD; echo '=== Pi beendet ==='; read" fi # Tiled-Layout tmux select-layout -t "$SESSION:0" tiled echo -e "${C_GREEN}Pane '$SLUG' erstellt.${C_RESET}" echo -e " Arbeitsverzeichnis: ${C_DIM}$WORKDIR${C_RESET}" # Warten bis Pi bereit local TIMEOUT=30 local ELAPSED=0 local PATTERN="(auto)|(zai)|(openrouter)" if [ -n "$PROVIDER" ]; then local PSHORT=$(echo "$PROVIDER" | sed 's/[^a-zA-Z0-9]//g') PATTERN="$PATTERN|($PSHORT)" fi # Neueste Pane finden local NEW_PANE=$(tmux list-panes -t "$SESSION:0" -F '#{pane_index}' | tail -1) echo -n " Warte auf Pi" while [ $ELAPSED -lt $TIMEOUT ]; do if ! tmux has-session -t "$SESSION" 2>/dev/null; then echo -e "\n ${C_RED}Session abgestürzt.${C_RESET}" exit 1 fi local CAPTURE=$(tmux capture-pane -t "$SESSION:0.$NEW_PANE" -p 2>/dev/null | tail -3) if echo "$CAPTURE" | grep -a -qE "$PATTERN" 2>/dev/null; then break fi echo -n "." sleep 1 ELAPSED=$((ELAPSED + 1)) done echo "" if [ $ELAPSED -ge $TIMEOUT ]; then echo -e " ${C_YELLOW}Timeout, versuche trotzdem...${C_RESET}" fi # Intercom-Namen setzen tmux send-keys -t "$SESSION:0.$NEW_PANE" "/name ${SLUG}" Enter sleep 1 # Aufgabe senden if [ -n "$TASK" ]; then tmux send-keys -t "$SESSION:0.$NEW_PANE" "$TASK" Enter echo -e " Aufgabe gesendet." fi echo -e " Wechseln: ${C_CYAN}Ctrl+B Pfeiltasten${C_RESET} | Vollbild: ${C_CYAN}Ctrl+B z${C_RESET}" } # --- List --- cmd_list() { if ! tmux has-session -t "$SESSION" 2>/dev/null; then echo "Keine aktive Session." exit 0 fi local COUNT=$(tmux list-panes -t "$SESSION:0" 2>/dev/null | wc -l) echo -e "${C_CYAN}Session '$SESSION' — $COUNT Panes (tiled):${C_RESET}" echo "" tmux list-panes -t "$SESSION:0" -F '#{pane_index}: #{pane_current_command} #{pane_active}' | while read line; do local IDX=$(echo "$line" | cut -d: -f1 | tr -d ' ') local CMD=$(echo "$line" | awk '{print $2}') local ACTIVE=$(echo "$line" | awk '{print $NF}') if [ "$ACTIVE" = "1" ]; then echo -e " ${C_GREEN}▶ Pane $IDX: $CMD${C_RESET}" else echo -e " Pane $IDX: $CMD" fi done } # --- Kill --- cmd_kill() { local NAME="${1:-}" if [ -z "$NAME" ]; then echo "Fehler: Name erforderlich." exit 1 fi # Pane anhand des working-directory finden local SLUG=$(echo "$NAME" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-') local TARGET_PANE="" for pane in $(tmux list-panes -t "$SESSION:0" -F '#{pane_index} #{pane_current_path}'); do local IDX=$(echo "$pane" | awk '{print $1}') local PATH_PANE=$(echo "$pane" | awk '{print $2}') if echo "$PATH_PANE" | grep -q "$SLUG"; then TARGET_PANE="$IDX" break fi done if [ -n "$TARGET_PANE" ]; then tmux kill-pane -t "$SESSION:0.$TARGET_PANE" tmux select-layout -t "$SESSION:0" tiled echo -e "${C_GREEN}Pane '$NAME' geschlossen.${C_RESET}" else echo -e "${C_RED}Pane '$NAME' nicht gefunden.${C_RESET}" fi } # --- Killall --- cmd_killall() { if tmux has-session -t "$SESSION" 2>/dev/null; then tmux kill-session -t "$SESSION" echo -e "${C_GREEN}Session '$SESSION' beendet.${C_RESET}" else echo "Keine aktive Session." fi } # --- Main --- CMD="${1:-}" shift || true case "$CMD" in init) cmd_init ;; spawn) cmd_spawn "$@" ;; list) cmd_list ;; kill) cmd_kill "$@" ;; killall) cmd_killall ;; help|--help|-h) usage ;; "") usage ;; *) echo "Unbekannt: $CMD"; usage; exit 1 ;; esac