Override ^p/^n in agent session to switch between root windows

This commit is contained in:
2026-09-14 11:55:27 +01:00
parent 4ac16ee7b4
commit 9ce131462f
4 changed files with 242 additions and 3 deletions

90
agent-sync.sh Executable file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Move the client that opened the agent popup to the root window matching the
# agent session's current window. Installed by agent.sh as the agent session's
# session-window-changed hook, so it runs for any window change made inside the
# popup: <prefix>C-n, <prefix>C-p, select-window, choose-tree or the mouse.
#
# Set @agent_follow_session to "off" to keep the opening client in the session
# it is already on, never switching it to the session owning the match.
set -e
# Fields are joined with a unit separator, not a tab: `read` folds runs of tabs
# and spaces even when IFS names them, so an unset option in the middle would
# silently shift every field after it.
us=$'\x1f'
# Everything recorded about the agent session's current window, in one
# round-trip. Format lookups rather than show-option, so unset user options come
# back empty instead of logging "invalid option" into the tmux message log.
state=$(tmux display-message -p -t agent "#{session_attached}$us#{@agent_dir}\
$us#{@agent_origin_window}$us#{@agent_popup_client}$us#{@agent_follow_session}" \
2>/dev/null || true)
IFS=$us read -r attached agent_dir origin_window popup_client follow <<<"$state"
# Only sync while a popup is actually attached. Without this guard the
# detach-client + kill-window agent-cmd.sh performs when an agent exits would
# drag the root client to whichever window tmux picks next.
[ "${attached:-0}" -gt 0 ] || exit 0
[ -n "$agent_dir" ] || exit 0
# An empty session means the recorded client has since gone away.
client_session=""
if [ -n "$popup_client" ]; then
client_session=$(tmux list-clients -F "#{client_name}$us#{session_name}" \
| awk -v FS="$us" -v client="$popup_client" '$1 == client { print $2; exit }')
fi
target_window=""
target_session=""
# Prefer the recorded origin window, as long as it still exists.
if [ -n "$origin_window" ]; then
while IFS=$us read -r window_id session_name; do
if [ "$window_id" = "$origin_window" ] && [ "$session_name" != agent ]; then
target_window=$window_id
target_session=$session_name
break
fi
done < <(tmux list-windows -a -F "#{window_id}$us#{session_name}")
fi
# Otherwise match the recorded directory against root windows. This covers agent
# windows created before origin recording existed, and root windows that have
# been recreated since.
if [ -z "$target_window" ]; then
seen_window=""
while IFS=$us read -r window_id session_name start_path current_path; do
# Panes are listed grouped by window in index order, so the first row for a
# window is its first pane.
[ "$window_id" != "$seen_window" ] || continue
seen_window=$window_id
[ "$session_name" != agent ] || continue
[ "$start_path" = "$agent_dir" ] || [ "$current_path" = "$agent_dir" ] || continue
# Prefer a window in the session the opening client is already on.
if [ "$session_name" = "$client_session" ]; then
target_window=$window_id
target_session=$session_name
break
fi
if [ -z "$target_window" ]; then
target_window=$window_id
target_session=$session_name
fi
done < <(tmux list-panes -a -F "#{window_id}$us#{session_name}\
$us#{pane_start_path}$us#{pane_current_path}")
fi
# No match: leave the root sessions alone rather than inventing a window.
[ -n "$target_window" ] || exit 0
# Window ids are server-global, so this moves the owning session whichever
# client happens to be on it.
tmux select-window -t "$target_window" 2>/dev/null || exit 0
# Selecting is enough when the client is already on the target session.
[ -n "$client_session" ] || exit 0
[ "$follow" != off ] || exit 0
if [ -n "$target_session" ] && [ "$target_session" != "$client_session" ]; then
tmux switch-client -c "$popup_client" -t "$target_session"
fi