From acd15ed1448392a111829663f16349c19c2967ea Mon Sep 17 00:00:00 2001 From: code-infra-01 Date: Fri, 4 Sep 2026 16:51:15 -0500 Subject: [PATCH] fix(tmux): compose draft-transition with landed box detection (#1332, O1) 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. --- .../framework/tools/tmux/send-message.sh | 75 ++++++++++++++++--- .../tools/tmux/test-send-message-verdict.sh | 51 +++++++++++++ 2 files changed, 115 insertions(+), 11 deletions(-) diff --git a/packages/mosaic/framework/tools/tmux/send-message.sh b/packages/mosaic/framework/tools/tmux/send-message.sh index 4e68a74e..0fb1e135 100755 --- a/packages/mosaic/framework/tools/tmux/send-message.sh +++ b/packages/mosaic/framework/tools/tmux/send-message.sh @@ -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 diff --git a/packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh b/packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh index fa1f515f..1559920f 100755 --- a/packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh +++ b/packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh @@ -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 ]