1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/usr/bin/env bash

# Picker for running Claude Code and pi sessions, ranked so the ones blocked
# on a human come first — barring any already jumped to and left blocked, which
# ,agent-state sinks to the bottom of that group. Bound to prefix+A in
# tmux.conf, where it runs in the same floating pane as ,z and ,tmux-grab.
# State comes from ,agent-state, which the Claude Code mod and the pi extension
# write to.

set -euo pipefail
if [[ "${TRACE-0}" == "1" ]]; then
    set -o xtrace
fi

main() {
    local state_script
    state_script="$(dirname "${BASH_SOURCE[0]}")/,agent-state"

    local rows
    rows=$("$state_script" list)
    if [[ -z $rows ]]; then
        tmux display-message 'No agent sessions tracked'
        exit 0
    fi

    # Pad the display columns here rather than in ,agent-state, so that script
    # stays a plain data source. Colour is applied after padding, since escape
    # bytes would otherwise count toward the field width. The pane id is left
    # visible as the trailing field: hiding it with --with-nth would make {2}
    # in --preview ambiguous about whether it indexes the original line or the
    # displayed one, and seeing the pane is mildly useful anyway.
    local lines
    lines=$(awk -F'\t' '
        {
            state = sprintf("%-7s", $1)
            if      ($1 == "blocked") state = "\033[31m" state "\033[0m"
            else if ($1 == "done")    state = "\033[33m" state "\033[0m"
            else                      state = "\033[2m"  state "\033[0m"
            # A blocked row without a message still says what it is waiting on.
            message = $5 != "" ? $5 : $8
            # Pane id leads so the delimiting tab lands at a fixed column; after
            # the variable-length message it would leave a ragged edge.
            printf "%s\t%s %-11s %-16s %-14s %5s  %s\n", $6, state, $7, $2, $3, $4, message
        }
    ' <<<"$rows")

    # Float via fzf's own tmux popup, unless the caller already gave us a
    # floating container, where --tmux would stack a redundant popup on top.
    # FZF_DEFAULT_OPTS (.zshrc) carries its own --tmux default, so it's cleared
    # here too — otherwise it fires regardless of our own flag.
    local tmux_opts=(--tmux "80%,60%")
    [[ -n "${IN_TMUX_FLOAT:-}" ]] && tmux_opts=()

    local selection
    # ${arr[@]+"${arr[@]}"} splices the array only when it has elements; a bare
    # "${arr[@]}" aborts under `set -u` on macOS bash 3.2, which counts an
    # empty array as unbound.
    # No --select-1, unlike ,z and ,tmux-grab. The pane this runs from has a row
    # too, so with a single session it would auto-jump to where the user already
    # is: the float would blink and nothing would appear to happen.
    selection=$(printf '%s\n' "$lines" | FZF_DEFAULT_OPTS='' fzf --no-multi \
        --no-margin --no-padding \
        ${tmux_opts[@]+"${tmux_opts[@]}"} \
        --ansi --no-sort \
        --layout reverse \
        --height 100% \
        --highlight-line --info inline-right \
        --delimiter '\t' \
        --border-label ' agents ' \
        --header 'enter: jump to session' --header-first \
        --preview 'tmux capture-pane -p -t {1} | tail -n 50' \
        --bind 'ctrl-/:change-preview-window(down|hidden|)') || exit 0

    local pane
    pane=$(cut -f1 <<<"$selection")
    [[ -n $pane ]] || exit 0

    # A pane target resolves to its session, window and pane in one step. The
    # three-step form races: switch-client returns before the client has moved,
    # so a follow-up select-window can land on the destination session's
    # last-active window instead.
    if ! tmux switch-client -t "$pane" 2>/dev/null; then
        tmux display-message "agents: pane $pane is gone"
        return 0
    fi

    # Recorded only once the jump lands, so a dead pane doesn't get demoted on
    # the way out. A visit can turn done into idle, so sketchybar redraws too.
    "$state_script" visited "$pane" || true
    local sketchybar_bin
    sketchybar_bin=$(command -v sketchybar) ||
        sketchybar_bin=/opt/homebrew/opt/sketchybar/bin/sketchybar
    if [[ -x $sketchybar_bin ]]; then
        "$sketchybar_bin" --trigger agent_state_change >/dev/null 2>&1 || true
    fi
}

main "$@"