framework tools/tmux: agent-send socket default resolution + ambiguity guard (B1)
ci/woodpecker/pr/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
Measured defect (2026-08-28/29): tasking sends without -L landed in a stale default-socket TWIN of the target seat; rc 0 reported honest delivery to the wrong pane. Resolution order now: explicit -L > MOSAIC_TMUX_SOCKET (launcher-exported) > unique socket hit; multiple hits refuse with rc 4 naming the sockets. Local targets only; remote pass-through unchanged. Socket test gains arms A (twin session on two sockets -> rc 4 with the socket list) and B (unique session auto-resolves and delivers on the right socket, no cross-delivery). Test remains CI-excluded per its signed exclusion (no tmux in the CI image, #1017).
This commit is contained in:
@@ -154,6 +154,37 @@ FULL="${PREAMBLE} ${MSG}"
|
||||
B64=$(printf '%s' "$FULL" | base64 -w0)
|
||||
|
||||
vflag=""; [ "$VERBOSE" = 1 ] && vflag="-v"
|
||||
|
||||
# Socket default resolution (B1, 2026-08-29): explicit -L wins; else the
|
||||
# launcher-exported MOSAIC_TMUX_SOCKET; else the tmux default socket. When
|
||||
# the target session exists on MULTIPLE socket servers and no explicit -L
|
||||
# was given, refuse with a disambiguation error instead of silently picking
|
||||
# one (measured 2026-08-28/29: tasking sends landed in a stale default-
|
||||
# socket twin of the target seat; rc 0 reported honest delivery to the
|
||||
# wrong pane). Local targets only; remote targets pass -L through as-is.
|
||||
if [ -z "$SOCKET_NAME" ] && [ -z "$SSH_TARGET" ]; then
|
||||
socket_dir="${TMPDIR:-/tmp}/tmux-$(id -u)"
|
||||
hits=""
|
||||
for sf in "$socket_dir"/*; do
|
||||
[ -S "$sf" ] || continue
|
||||
sname="${sf##*/}"
|
||||
tmux -L "$sname" has-session -t "$DST_SESSION" 2>/dev/null && hits="$hits $sname"
|
||||
done
|
||||
hit_count=$(printf '%s' "$hits" | wc -w)
|
||||
if [ "$hit_count" -gt 1 ]; then
|
||||
echo "agent-send.sh: REFUSING - session '$DST_SESSION' exists on multiple sockets:$hits" >&2
|
||||
echo " Pass -L <socket> explicitly (or set MOSAIC_TMUX_SOCKET)." >&2
|
||||
exit 4
|
||||
elif [ "$hit_count" -eq 1 ]; then
|
||||
SOCKET_NAME="${hits# }"
|
||||
elif [ -n "${MOSAIC_TMUX_SOCKET:-}" ]; then
|
||||
SOCKET_NAME="$MOSAIC_TMUX_SOCKET"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$SOCKET_NAME" ] && [ -n "${MOSAIC_TMUX_SOCKET:-}" ] && [ -n "$SSH_TARGET" ]; then
|
||||
SOCKET_NAME="$MOSAIC_TMUX_SOCKET"
|
||||
fi
|
||||
|
||||
socket_args=()
|
||||
if [ -n "$SOCKET_NAME" ]; then
|
||||
socket_args=(-L "$SOCKET_NAME")
|
||||
|
||||
@@ -8,7 +8,8 @@ SOCKET="mosaic-test-$RANDOM-$$"
|
||||
TARGET="target-$RANDOM"
|
||||
DEFAULT_TARGET="default-target-$RANDOM"
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; tmux kill-session -t "$DEFAULT_TARGET" >/dev/null 2>&1 || true; rm -rf "$TMPDIR"' EXIT
|
||||
TWIN="twin-$RANDOM-$$"
|
||||
trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; tmux kill-session -t "$DEFAULT_TARGET" >/dev/null 2>&1 || true; tmux kill-session -t "$TWIN" >/dev/null 2>&1 || true; rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
@@ -79,4 +80,32 @@ for i in $(seq 1 "$CONC_N"); do
|
||||
done
|
||||
done
|
||||
|
||||
# B1 (2026-08-29): socket default resolution in agent-send.sh. Measured
|
||||
# defect: tasking sends without -L landed in a stale default-socket twin of
|
||||
# the target seat; rc 0 reported honest delivery to the wrong pane.
|
||||
|
||||
# Arm A: session on MULTIPLE sockets, no -L -> refuse with rc 4 naming both.
|
||||
tmux -L "$SOCKET" new-session -d -s "$TWIN" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i'
|
||||
tmux new-session -d -s "$TWIN" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i'
|
||||
amb_rc=0
|
||||
"$AGENT_SEND" -s "$TWIN" -m "must refuse" >/tmp/amb.out 2>/tmp/amb.err || amb_rc=$?
|
||||
[ "$amb_rc" -eq 4 ] || fail "ambiguity refusal: rc=$amb_rc want 4 (stderr: $(cat /tmp/amb.err))"
|
||||
grep -q "multiple sockets" /tmp/amb.err || fail "ambiguity refusal message missing socket list"
|
||||
grep -qF "$SOCKET" /tmp/amb.err || fail "ambiguity refusal message does not name the test socket"
|
||||
tmux kill-session -t "$TWIN" >/dev/null 2>&1 || true
|
||||
tmux -L "$SOCKET" kill-session -t "$TWIN" >/dev/null 2>&1 || true
|
||||
|
||||
# Arm B: session unique to ONE socket, no -L -> auto-resolve to that socket
|
||||
# and deliver there.
|
||||
uniq_rc=0
|
||||
"$AGENT_SEND" -s "$TARGET" -m "autoresolved hello" >/tmp/uniq.out 2>/tmp/uniq.err || uniq_rc=$?
|
||||
[ "$uniq_rc" -eq 0 ] || fail "unique auto-resolution: rc=$uniq_rc (stderr: $(cat /tmp/uniq.err))"
|
||||
sleep 0.2
|
||||
auto_pane="$(capture_named)" || fail "could not capture named socket pane (arm B)"
|
||||
grep -qF "autoresolved hello" <<<"$auto_pane" || fail "auto-resolution did not deliver to the named-socket pane"
|
||||
default_pane2="$(capture_default)" || fail "could not capture default socket pane (arm B)"
|
||||
if grep -qF "autoresolved hello" <<<"$default_pane2"; then
|
||||
fail "auto-resolution cross-delivered to the default socket pane"
|
||||
fi
|
||||
|
||||
echo "ok - named tmux socket send tools"
|
||||
|
||||
Reference in New Issue
Block a user