fix(tmux): compose draft-transition with landed box detection (#1332, O1)
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Rebased onto current next and composes the two sibling mechanisms per the O1 ruling: the cursor-row draft transition stays the authoritative runtime-agnostic delivered verdict; next's locate_input_box (two shapes: prompt glyph, pi rule box) is adopted as positive DRAFT evidence only, covering the cursor-row check's blind spot on redrawn TUIs that park the cursor off the input line. Box-absence proves nothing and can never produce UNDELIVERED: a shapeless-but-submitting pane delivers via the cursor-row transition regardless. The verdict suite gains fixtures 6 and 6b: the 2026-09-04 scratch probe is the regression test (shapeless REPL that submits must report delivered; shape probing alone exited 2 with retry advice on a consumed message), plus the raw/no-echo shapeless guard arm. Measured limit recorded in the suite: a shapeless cooked non-reading pane is indistinguishable from a delivering one by any runtime-agnostic signal available to the sender. Rerun evidence: pipefail scanner 7/7, glyph-agnostic suite 6/6, verdict suite 8/8, live shapeless probe rc=0 delivered with consumption proof.
This commit is contained in:
@@ -32,7 +32,11 @@
|
||||
# 0 delivered (submitted) or queued (agent busy; will process when free)
|
||||
# 1 tmux target not found
|
||||
# 2 submission NOT confirmed — either still an unsubmitted draft, or the REPL
|
||||
# input prompt could not be located to confirm the message actually landed.
|
||||
# input box could not be located to confirm the message actually landed.
|
||||
# Delivered verdicts are runtime-agnostic (cursor-row draft transition, or
|
||||
# the queued banner); locate_input_box() below adds positive DRAFT evidence
|
||||
# for panes that render a recognizable box, and never gates delivery on a
|
||||
# runtime's rendering shape.
|
||||
# Delivery is NEVER inferred from absence of evidence: if we cannot positively
|
||||
# see the input box clear of the message (or the queued banner), we fail loud
|
||||
# so the sender learns immediately instead of a silent worker->lead stall.
|
||||
@@ -119,6 +123,51 @@ _draft_on_input() { # true iff our message tail is sitting on the input line now
|
||||
grep -qF "$snippet" <<<"$(_cursor_line)"
|
||||
}
|
||||
|
||||
# Locate the REPL input box in a captured pane. Prints the box's contents on
|
||||
# stdout and returns 0 when the box was FOUND; returns 1 when it could not
|
||||
# be located at all. Found-but-empty is a real, distinct answer (an empty input
|
||||
# box is what a submitted message leaves behind), so the caller must branch on
|
||||
# the return code, never on whether the output is empty.
|
||||
#
|
||||
# Two REPL shapes are recognised:
|
||||
# * a prompt-glyph line — `❯`, a leading `>`, or `│ >`. Claude Code and most
|
||||
# readline REPLs.
|
||||
# * a box drawn as two horizontal `─` rules with the input between them and NO
|
||||
# prompt glyph anywhere. pi renders this. Anchoring on the LAST rule pair is
|
||||
# what makes it safe: agent output can contain its own rules, but nothing is
|
||||
# drawn below the input box except the status line.
|
||||
#
|
||||
# Compose authority rule (#1332 O1): this function is POSITIVE DRAFT EVIDENCE
|
||||
# ONLY. A located box still carrying our tail is affirmative proof the message
|
||||
# was not consumed (the cursor-row check's blind spot: a redrawn TUI can park
|
||||
# the cursor off the input line, which the draft-transition anchor cannot see).
|
||||
# Its failure to find a box proves NOTHING and must never produce an
|
||||
# UNDELIVERED verdict: a shapeless-but-submitting pane delivers via the
|
||||
# cursor-row transition regardless (measured, scratch probe 2026-09-04;
|
||||
# shapeless REPL consumed the message while shape probing alone reported
|
||||
# "may be UNDELIVERED" — the exact #1257 regression this split prevents).
|
||||
locate_input_box() {
|
||||
local pane=$1 glyph_line rule_lines top bottom
|
||||
glyph_line=$(printf '%s\n' "$pane" | grep -E '❯|^>|│ >' | tail -1)
|
||||
if [ -n "$glyph_line" ]; then printf '%s\n' "$glyph_line"; return 0; fi
|
||||
rule_lines=$(printf '%s\n' "$pane" | grep -nE '^[[:space:]]*─{4,}[[:space:]]*$' | cut -d: -f1 | tail -2)
|
||||
[ -n "$rule_lines" ] || return 1
|
||||
# Split the (at most two) captured line numbers with parameter expansion. Not
|
||||
# `head -1`: piping into an early-exiting consumer SIGPIPEs the producer, which
|
||||
# under `set -euo pipefail` aborts the caller with rc=141 and no output. The
|
||||
# scripts/pipefail-early-exit.test.mjs guard reds on that shape, correctly.
|
||||
# With one rule captured both halves resolve to the same value and the
|
||||
# ordering test below rejects it, which is the answer we want anyway.
|
||||
top=${rule_lines%%$'\n'*}
|
||||
bottom=${rule_lines##*$'\n'}
|
||||
[ "$top" != "$bottom" ] || return 1
|
||||
[ "$bottom" -gt "$top" ] || return 1
|
||||
# An empty range (adjacent rules) prints nothing and still returns 0: found,
|
||||
# empty, which is the delivered shape.
|
||||
printf '%s\n' "$pane" | sed -n "$((top + 1)),$((bottom - 1))p"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Baseline: after the paste, our draft must be on the input line. This is positive
|
||||
# proof we are on the right pane and the paste landed — the anchor the transition
|
||||
# check measures against.
|
||||
@@ -134,16 +183,20 @@ for attempt in $(seq 1 $((RETRIES + 1))); do
|
||||
if grep -qF "$QUEUED_RE" <<<"$pane"; then
|
||||
status="queued"; break
|
||||
fi
|
||||
# POSITIVE draft evidence from a located prompt box, when one exists. This is the
|
||||
# cursor-row check's blind spot: a pane in COOKED mode (a plain shell whose
|
||||
# foreground process never reads stdin) echoes our paste via the kernel line
|
||||
# discipline and moves the cursor off it on Enter, which is indistinguishable from
|
||||
# a real submit by cursor row alone. If a prompt box IS locatable and still carries
|
||||
# our tail, that is affirmative proof the message was not consumed. Absence of a
|
||||
# glyph is still never used for anything — that inference is the original E7 bug.
|
||||
promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1)
|
||||
if [ -n "$promptline" ] && [ -n "$snippet" ] && grep -qF "$snippet" <<<"$promptline"; then
|
||||
status="draft"; continue
|
||||
# POSITIVE draft evidence from a located input box, when one exists. This is
|
||||
# the cursor-row check's blind spot: a redrawn TUI (pi's box) can park the
|
||||
# cursor off the input line, which the draft-transition anchor cannot see,
|
||||
# while a pane in COOKED mode (a plain shell whose foreground process never
|
||||
# reads stdin) echoes our paste via the kernel line discipline and moves the
|
||||
# cursor off it on Enter, indistinguishable from a real submit by cursor row
|
||||
# alone. If a locatable box still carries our tail, that is affirmative proof
|
||||
# the message was not consumed. Absence of a recognizable shape is never used
|
||||
# for anything — that inference is the original E7 bug, and the delivered
|
||||
# verdict stays with the runtime-agnostic cursor-row transition.
|
||||
if inputbox=$(locate_input_box "$pane"); then
|
||||
if [ -n "$snippet" ] && grep -qF "$snippet" <<<"$inputbox"; then
|
||||
status="draft"; continue
|
||||
fi
|
||||
fi
|
||||
if [ "$saw_draft" = 1 ]; then
|
||||
if _draft_on_input; then
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
# 5. DRAFT — the same glyphless box, holding our tail across every flush
|
||||
# (box shape) Enter => exit 2, stderr "unsubmitted draft". Pre-#1362 this
|
||||
# also reported unconfirmed, so the true state was invisible.
|
||||
# 6. DELIVERED — a SHAPELESS REPL (no glyph, no box) that submits => exit 0.
|
||||
# The 2026-09-04 scratch probe regression: shape probing alone reports "may
|
||||
# be UNDELIVERED" on this pane while the message is consumed; the cursor-row
|
||||
# draft transition is the authoritative runtime-agnostic verdict.
|
||||
# 6b. UNCONFIRMED— a shapeless pane in raw/no-echo mode that never reads stdin
|
||||
# (shapeless) => exit 2 "could not confirm submission" (never delivered).
|
||||
set -uo pipefail
|
||||
|
||||
HERE=$(cd -- "$(dirname -- "$0")" && pwd)
|
||||
@@ -154,6 +160,51 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Fixtures 6 and 6b: a SHAPELESS REPL. The pane renders nothing at all: no
|
||||
# prompt glyph and no rule box, so locate_input_box() alone can never see it
|
||||
# and shape-probing alone reports "may be UNDELIVERED" on a delivered message
|
||||
# (measured live 2026-09-04, scratch probe: a shapeless consumer CONSUMED the
|
||||
# message while the shipped shape probe exited 2 with retry advice - the exact
|
||||
# #1257 regression). The cursor-row draft transition is the authoritative,
|
||||
# runtime-agnostic delivered verdict: fixture 6's consumer submits => exit 0.
|
||||
# Fixture 6b is the guard arm: a shapeless pane whose foreground never reads
|
||||
# stdin keeps the echoed paste on the cursor line across every flush Enter =>
|
||||
# DRAFT => exit 2, never delivered.
|
||||
cat > "$TMP/shapeless.py" <<'SHAPELESS'
|
||||
import sys
|
||||
for line in sys.stdin:
|
||||
pass # consume and render nothing
|
||||
SHAPELESS
|
||||
tmux -L "$SOCKET" new-session -d -s shapeless -c "$TMP" "exec python3 -u '$TMP/shapeless.py'"
|
||||
sleep 0.3
|
||||
out=$("$SEND" -L "$SOCKET" -t "=shapeless" -m "fixture six shapeless consumed ok" 2>"$TMP/e6"); rc=$?
|
||||
if [ "$rc" -eq 0 ] && grep -qF "✓ delivered" <<<"$out"; then
|
||||
ok "delivered: shapeless REPL that submits => exit 0 ✓ delivered (probe regression)"
|
||||
else
|
||||
no "delivered: shapeless REPL that submits => exit 0 ✓ delivered" "rc=$rc out=[$out] err=[$(cat "$TMP/e6")]"
|
||||
fi
|
||||
|
||||
# MEASURED LIMIT (2026-09-04, this suite's development): a shapeless pane in
|
||||
# COOKED mode whose foreground never reads stdin (e.g. 'sleep infinity') scrolls
|
||||
# its kernel echo off the cursor row on the flush Enter, so no runtime-agnostic
|
||||
# signal available to the sender distinguishes it from a delivering pane. The
|
||||
# non-reading guard therefore requires either a locatable box still carrying the
|
||||
# tail (fixture 3) or raw/no-echo mode (fixture 2b). Real REPL seats read stdin,
|
||||
# which is why this limit is not reachable against agent seats; recorded here so
|
||||
# nobody rediscovers it as a silent gap.
|
||||
tmux -L "$SOCKET" new-session -d -s shapelessraw -c "$TMP" 'stty raw -echo; exec sleep infinity'
|
||||
sleep 0.3
|
||||
if out=$("$SEND" -L "$SOCKET" -t "=shapelessraw" -r 1 -m "fixture six b shapeless raw never consumed" 2>"$TMP/e6b"); then
|
||||
no "unconfirmed: shapeless raw non-reading pane must NOT report success" "expected exit 2, got 0 (out=[$out])"
|
||||
else
|
||||
rc=$?
|
||||
if [ "$rc" -eq 2 ] && grep -qF "could not confirm submission" "$TMP/e6b"; then
|
||||
ok "unconfirmed: shapeless raw non-reading pane => exit 2 + 'could not confirm submission'"
|
||||
else
|
||||
no "unconfirmed: shapeless raw non-reading pane => exit 2 + stderr" "rc=$rc err=[$(cat "$TMP/e6b")]"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
echo "PASS=$PASS FAIL=$FAIL"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
|
||||
Reference in New Issue
Block a user