chore: consolidate new foundation and archive v1 (#1495)

This commit is contained in:
2026-09-07 12:32:57 -05:00
3511 changed files with 727899 additions and 10 deletions
@@ -0,0 +1,103 @@
# Inter-Agent tmux Comms — Standard & Tooling
Reliable, self-identifying messaging between Mosaic agents running in tmux panes
(Claude Code / Codex / OpenCode REPLs), across hosts.
## The addressing standard (required)
Every cross-agent tmux message MUST begin with an addressing preamble:
```
[<src_host>:<src_session> -> <dst_host>:<dst_session>] <message>
```
- `host` = `hostname -s` of the machine the agent runs on (e.g. `web1`, `sb-it-mgr-0-lt`).
- `session` = the tmux session name (e.g. `mos-claude`, `rev0-4`, `installer-1`).
- **Replies FLIP the preamble**: the recipient answers with `[<dst> -> <src>] ...`.
Why: a fresh or context-wiped agent always knows who sent a message and to whom.
No ambiguity about origin or lane after a tmux wipe / session restart.
Example exchange:
```
[web1:mos-claude -> sb-it-mgr-0-lt:installer-1] status on #29?
[sb-it-mgr-0-lt:installer-1 -> web1:mos-claude] Q2 done, opening PR #34.
```
## The helper: `agent-send.sh`
Prepends the preamble automatically (auto-detecting your own `host:session`) and
delivers reliably to local OR remote panes.
```bash
# Local target (same host, default tmux server)
agent-send.sh -s <dst_session> -m "message"
# Local target on a Mosaic fleet socket
agent-send.sh -L mosaic-fleet -s '=coder0' -m "message"
# Remote target (over ssh)
agent-send.sh -H user@host -s <dst_session> -m "message"
# From a file / stdin
agent-send.sh -H user@host -s <dst_session> -f msg.txt
echo "msg" | agent-send.sh -s <dst_session>
```
Key flags: `-L` named tmux socket · `-s` dst session (required) · `-H` ssh target for remote · `-n` dst
hostname for the preamble (else auto-resolved) · `-m`/`-f`/stdin body · `-S`
override source label · `-v` verbose · `-r N` Enter-flush attempts.
For durable fleet use, prefer exact tmux targets such as `=coder0`. The helper
normalizes exact session targets to pane-qualified targets internally so pane
commands do not fall back to tmux's prefix matching behavior.
## Named socket isolation
Durable Mosaic fleets should use a dedicated tmux socket, for example:
```bash
tmux -L mosaic-fleet ls
agent-send.sh -L mosaic-fleet -s '=coder0' -m "status?"
send-message.sh -L mosaic-fleet -t '=coder0' -m "raw pane message"
```
This keeps fleet operations away from the user's default tmux server. It is the
safe rollout path on hosts that already have manual tmux sessions.
## Why a helper exists (the submission gotcha)
Pasting into an interactive REPL via raw `tmux send-keys` is unreliable: a
trailing `Enter` is frequently swallowed and the message sits as an **unsubmitted
draft** ("Press up to edit queued messages"). Over an `ssh -> nested tmux` hop the
plain `Enter` keyname often does not register at all — `C-m` is needed.
`send-message.sh` solves this for a **local** pane: bracketed-paste the body
(so multi-line content doesn't submit early), pause, then send `Enter` as its own
keystroke and flush with a second, verifying against a draft heuristic.
`agent-send.sh` solves the **remote** case by _shipping `send-message.sh` over ssh_
(`ssh host bash -s -- ... < send-message.sh`) and running it local to the target
pane — so the reliable send-keys always happens on the pane's own host. The remote
needs only `bash` + `tmux` + `base64`; **no mosaic install required there**. The
message crosses the wire as base64 (`-b`) to avoid all shell-quoting hazards.
## Files
- `agent-send.sh` — inter-agent wrapper (preamble + local/remote dispatch).
- `send-message.sh` — low-level reliable single-pane submitter (`-b` base64 input).
- `auto-submit-drafts.sh` — watchdog that flushes stable unsubmitted prompt
drafts on a coordinator pane (default target `mos-claude`); run it as a
long-lived process alongside the coordinator session.
- `agent-send.test.sh` — regression + grammar lock for `agent-send.sh`.
- `test-send-message-socket.sh` — smoke test for named-socket isolation.
## Distribution
These live in the installed framework copy at
`~/.config/mosaic/tools/tmux/`. `install.sh` rsyncs the framework **source tree**
to each host, so to propagate permanently, land both files in the framework
source repo and re-run the installer on each host. Until then, `agent-send.sh`
already works against any reachable host because it ships `send-message.sh` over
ssh per-send — no pre-install on the target host is needed to _send to_ it.
+229
View File
@@ -0,0 +1,229 @@
#!/usr/bin/env bash
# agent-send.sh — standard inter-agent tmux messaging for the Mosaic stack.
#
# WHAT IT DOES
# Sends a message to another agent's tmux pane (local or on a remote host)
# with the canonical addressing preamble prepended:
#
# [<src_host>:<src_session> -> <dst_host>:<dst_session>] <message>
#
# The preamble makes every inter-agent message self-identifying, so a fresh
# or context-wiped agent always knows who sent a message and to whom — no
# ambiguity about lanes or origin. Recipients replying should FLIP the
# preamble: [<dst> -> <src>] ... (this tool sends; it does not auto-reply).
#
# Optionally tags the message with a TRIAGE CLASS (see -C / --class) so a
# comms daemon can route it (deliver-to-agent vs log-and-drop) from an exact
# field instead of re-deriving intent from the body.
#
# WHY A WRAPPER
# Reliable submission into an interactive REPL (Claude Code / Codex) is fiddly:
# a trailing Enter is often swallowed and the message sits as an unsubmitted
# DRAFT. tools/tmux/send-message.sh already solves that for a LOCAL pane via
# bracketed-paste + Enter-flush + draft-detection. For REMOTE targets this
# wrapper SHIPS send-message.sh over ssh (stdin) and runs it there, so the
# reliable send-keys happens local to the target pane — sidestepping the
# ssh->nested-tmux Enter/C-m swallow entirely. No mosaic install needed on
# the remote host; only bash + tmux + base64 (standard).
#
# USAGE
# agent-send.sh [-L socket] -s <dst_session> -m "message" # local target
# agent-send.sh [-L socket] -H user@host -s <dst_session> -m "message" # remote target
# agent-send.sh [-L socket] -H user@host -n <dst_hostname> -s <sess> -f msg.txt
# agent-send.sh -s mos-claude --class terminal-log -m "ACK — received"
# echo "msg" | agent-send.sh [-L socket] -H user@host -s <dst_session>
#
# OPTIONS
# -L NAME tmux socket name passed to `tmux -L NAME` on the target host
#
# Exit 4: local target session exists on multiple socket servers and no
# -L / MOSAIC_TMUX_SOCKET disambiguated it (B1 stale-twin guard).
# -s DST_SESSION target tmux session (or session:window.pane) [required]
# -H SSH_TARGET ssh target (user@host) for a remote pane; omit for local
# -n DST_HOST hostname to show in the preamble for the target.
# Default: local hostname, or (remote) resolved via one ssh.
# -m MESSAGE message text (single- or multi-line)
# -f FILE read message from FILE instead of -m
# -C CLASS triage class for a comms daemon. One of:
# terminal-log log-only; never needs the agent's attention
# actionable carries a decision/blocker/gate — deliver
# human from a human operator — deliver
# reaction an emoji/ack reaction
# digest machine-wake, coalescible; batched wake/heartbeat signal
# Long form: --class CLASS (or --class=CLASS). When SET, the
# preamble carries a ` class=<CLASS>` token INSIDE the bracket:
# [<src> -> <dst> class=terminal-log] <message>
# When OMITTED, NO token is emitted and the preamble is
# byte-for-byte identical to the classic format. Consumers MUST
# treat an absent class as 'actionable' (fail-safe: agent sees it).
# -S SRC_LABEL override source label "<host>:<session>" (default: auto)
# -r N Enter-flush attempts passed through (default 2)
# -v verbose: print pane tail after delivery
# -h help
#
# PREAMBLE GRAMMAR (for consumers / daemons mirroring this producer)
# ^\[(\S+) -> (\S+?)(?: class=(terminal-log|actionable|human|reaction|digest))?\] (.*)$
# group 1 = src label group 2 = dst host:session
# group 3 = class (absent => actionable) group 4 = message body
#
# EXIT CODES (passed through from send-message.sh, except 4)
# 0 delivered/queued · 1 target not found · 2 still draft · 3 usage error
# 4 agent-send refusal: local target session exists on multiple socket
# servers and no -L / MOSAIC_TMUX_SOCKET disambiguated it (B1)
set -uo pipefail
SELF_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
# Sender is overridable via env purely for testing (inject a capture stub). The
# default is the canonical send-message.sh beside this script; production callers
# never set AGENT_SEND_SENDER, so behavior is unchanged.
SENDER="${AGENT_SEND_SENDER:-$SELF_DIR/send-message.sh}"
# Translate the long option --class[=value] into "-C value" so getopts (which is
# short-option-only) can parse it. Every other argument passes through untouched,
# so callers that never use --class hit the exact original getopts path.
args=()
while [ $# -gt 0 ]; do
case "$1" in
--class) [ $# -ge 2 ] || { echo "ERROR: --class requires a value" >&2; exit 3; }
args+=(-C "$2"); shift 2 ;;
--class=*) args+=(-C "${1#*=}"); shift ;;
*) args+=("$1"); shift ;;
esac
done
set -- ${args[@]+"${args[@]}"}
DST_SESSION=""; SSH_TARGET=""; DST_HOST=""; MSG=""; FILE=""; SOCKET_NAME=""
SRC_LABEL=""; RETRIES=2; VERBOSE=0; CLASS=""
usage() { sed -n '2,/^set -uo pipefail/{/^set -uo pipefail/d;p}' "$0"; exit "${1:-3}"; }
while getopts "L:s:H:n:m:f:S:r:C:vh" o; do
case "$o" in
L) SOCKET_NAME=$OPTARG ;;
s) DST_SESSION=$OPTARG ;; H) SSH_TARGET=$OPTARG ;; n) DST_HOST=$OPTARG ;;
m) MSG=$OPTARG ;; f) FILE=$OPTARG ;; S) SRC_LABEL=$OPTARG ;;
C) CLASS=$OPTARG ;;
r) RETRIES=$OPTARG ;; v) VERBOSE=1 ;; h) usage 0 ;; *) usage 3 ;;
esac
done
[ -n "$DST_SESSION" ] || { echo "ERROR: -s DST_SESSION is required" >&2; usage 3; }
[ -x "$SENDER" ] || { echo "ERROR: send-message.sh not found beside this script" >&2; exit 3; }
# Validate the triage class only when one was given. An absent class emits NO
# token (preamble byte-identical to the classic format); the consumer defaults
# absent => actionable.
CLASS_TOKEN=""
if [ -n "$CLASS" ]; then
case "$CLASS" in
terminal-log|actionable|human|reaction|digest) CLASS_TOKEN=" class=${CLASS}" ;;
*) echo "ERROR: invalid --class '$CLASS' (allowed: terminal-log, actionable, human, reaction, digest)" >&2; exit 3 ;;
esac
fi
# Message body from -f / -m / stdin.
if [ -n "$FILE" ]; then [ -r "$FILE" ] || { echo "ERROR: cannot read $FILE" >&2; exit 3; }; MSG=$(cat -- "$FILE")
elif [ -z "$MSG" ] && [ ! -t 0 ]; then MSG=$(cat)
fi
[ -n "$MSG" ] || { echo "ERROR: empty message (use -m, -f, or stdin)" >&2; exit 3; }
# Source label: this agent's host:session (auto-detected, overridable).
if [ -z "$SRC_LABEL" ]; then
src_host=$(hostname -s 2>/dev/null || echo "?")
src_sess=${MOSAIC_AGENT_NAME:-}
if [ -z "$src_sess" ]; then
if [ -n "${TMUX:-}" ]; then
# Inside tmux: display-message resolves against this client's own session.
src_sess=$(tmux display-message -p '#S' 2>/dev/null || echo "?")
else
# Outside tmux with no name: display-message reports the LAST-ACTIVE
# session — someone else's identity (measured 2026-08-20: a nameless
# non-tmux sender was stamped "peggy", a live seat, forged silently).
# Stamp an explicit unverified label instead; deliberate senders use -S.
src_sess="unverified"
fi
fi
SRC_LABEL="${src_host}:${src_sess}"
fi
# Destination host label for the preamble.
if [ -z "$DST_HOST" ]; then
if [ -n "$SSH_TARGET" ]; then
DST_HOST=$(ssh -o ConnectTimeout=8 -o BatchMode=yes "$SSH_TARGET" 'hostname -s' 2>/dev/null || echo "${SSH_TARGET#*@}")
else
DST_HOST=$(hostname -s 2>/dev/null || echo "local")
fi
fi
PREAMBLE="[${SRC_LABEL} -> ${DST_HOST}:${DST_SESSION}${CLASS_TOKEN}]"
FULL="${PREAMBLE} ${MSG}"
B64=$(printf '%s' "$FULL" | base64 -w0)
vflag=""; [ "$VERBOSE" = 1 ] && vflag="-v"
# Exact session matching for the sender target (codex PR #1466): without
# '=', tmux target syntax accepts an unambiguous PREFIX, so a delivery
# aimed at session X can land in X-old. Compound targets (session:win.pane)
# and already-exact ('=...') forms pass through untouched. Computed BEFORE
# socket discovery so the discovery probes use the same target semantics
# (probing '==name' for an already-exact input was a false-negative hit).
DST_TARGET="$DST_SESSION"
case "$DST_SESSION" in
=*) ;;
*:*)
# Compound target (session:win.pane): pin the SESSION component exact
# (=session:win.pane); unpinned, the session part still prefix-matches
# (codex PR #1466: 'agent:0.0' can resolve into 'agent-old').
DST_TARGET="=${DST_SESSION%%:*}:${DST_SESSION#*:}"
;;
*) DST_TARGET="=$DST_SESSION" ;;
esac
# Socket default resolution (B1, 2026-08-29). Precedence: explicit -L >
# launcher-exported MOSAIC_TMUX_SOCKET > unique socket hit > refusal on
# ambiguity > tmux default socket. The ambiguity refusal fires ONLY when
# no explicit or env choice exists and the session name lives on multiple
# servers (measured 2026-08-28/29: tasking sends landed in a stale
# default-socket twin; rc 0 reported honest delivery to the wrong pane).
# Socket discovery scans tmux's own socket dir, ${TMUX_TMPDIR:-/tmp}/tmux-UID
# (codex PR #1466: TMPDIR is not where tmux keeps -L sockets).
# MOSAIC_TMUX_SOCKET is LOCAL-host state (launcher-exported): it must not
# leak into remote sends, where -L would target a socket on the remote
# host (codex PR #1466).
if [ -z "$SOCKET_NAME" ] && [ -z "$SSH_TARGET" ] && [ -n "${MOSAIC_TMUX_SOCKET:-}" ]; then
SOCKET_NAME="$MOSAIC_TMUX_SOCKET"
fi
if [ -z "$SOCKET_NAME" ] && [ -z "$SSH_TARGET" ]; then
socket_dir="${TMUX_TMPDIR:-/tmp}/tmux-$(id -u)"
hits=""
for sf in "$socket_dir"/*; do
[ -S "$sf" ] || continue
sname="${sf##*/}"
# '=' forces exact session-name matching: tmux target syntax otherwise
# accepts an unambiguous PREFIX, so a session named X-old on a socket
# would count as a false hit for target X (codex PR #1466).
tmux -L "$sname" has-session -t "$DST_TARGET" 2>/dev/null && hits="$hits$sname"$'\n'
done
hit_count=$(printf '%s' "$hits" | grep -c . || true)
if [ "$hit_count" -gt 1 ]; then
echo "agent-send.sh: REFUSING - session '$DST_SESSION' exists on multiple sockets:" >&2
printf ' %s\n' $hits >&2
echo " Pass -L <socket> explicitly (or export MOSAIC_TMUX_SOCKET to disambiguate)." >&2
exit 4
elif [ "$hit_count" -eq 1 ]; then
SOCKET_NAME="$(printf '%s' "$hits")"
fi
fi
socket_args=()
if [ -n "$SOCKET_NAME" ]; then
socket_args=(-L "$SOCKET_NAME")
fi
if [ -z "$SSH_TARGET" ]; then
# Local pane: call the canonical sender directly.
exec "$SENDER" "${socket_args[@]}" -t "$DST_TARGET" -b "$B64" -r "$RETRIES" $vflag
else
# Remote pane: ship the sender over ssh and run it local to the target.
ssh -o ConnectTimeout=10 "$SSH_TARGET" \
"bash -s -- ${socket_args[*]@Q} -t '$DST_TARGET' -b '$B64' -r '$RETRIES' $vflag" < "$SENDER"
fi
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# agent-send.test.sh — regression + grammar lock for agent-send.sh --class.
#
# Strategy: inject a capture stub via AGENT_SEND_SENDER that decodes the -b
# base64 payload and prints the FULL message (preamble + body) so we can assert
# the exact bytes on the wire. Local path only (no ssh), -n pins the dst host so
# the preamble is deterministic across machines.
#
# Guarantees locked here:
# 1. REGRESSION BAR — no --class => preamble byte-for-byte identical to classic.
# 2. --class <c> => ` class=<c>` token emitted inside the bracket.
# 3. --class=<c> (equals form) parses identically to the space form.
# 4. -C <c> short form parses identically.
# 5. invalid class => exit 3, nothing sent.
# 6. --class with no value => exit 3.
# 7. the documented consumer regex parses producer output for every class.
# 8. MOSAIC_AGENT_NAME is authoritative for sender identity.
# 9. sender fallback queries local tmux, never the destination -L socket.
# 10. an undeterminable sender is stamped as "?".
# 11. --class digest is accepted (machine-wake, coalescible canon class).
# 12. -C digest short form parses identically.
# 13. the documented consumer regex parses producer output for class=digest.
set -uo pipefail
HERE=$(cd -- "$(dirname -- "$0")" && pwd)
TOOL="$HERE/agent-send.sh"
# Capture stub: stands in for send-message.sh. Decodes -b and prints the payload.
STUB=$(mktemp)
FAKE_BIN=$(mktemp -d)
trap 'rm -f "$STUB"; rm -rf "$FAKE_BIN"' EXIT
cat >"$STUB" <<'STUB_EOF'
#!/usr/bin/env bash
set -uo pipefail
b64=""
while getopts "L:t:b:r:v" o; do case "$o" in b) b64=$OPTARG ;; *) : ;; esac; done
printf '%s' "$b64" | base64 -d
STUB_EOF
chmod +x "$STUB"
# Fake tmux distinguishes the sender's default socket from a destination socket.
cat >"$FAKE_BIN/tmux" <<'TMUX_EOF'
#!/usr/bin/env bash
set -uo pipefail
case "${FAKE_TMUX_MODE:-sessions}" in
unavailable) exit 1 ;;
sessions)
if [ "${1:-}" = "-L" ]; then
printf '%s\n' 'destination-holder'
else
printf '%s\n' 'local-agent'
fi
;;
esac
TMUX_EOF
chmod +x "$FAKE_BIN/tmux"
PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); printf 'ok %s\n' "$1"; }
no() { FAIL=$((FAIL+1)); printf 'FAIL %s\n %s\n' "$1" "$2"; }
# Run the tool with the stub injected; echoes captured payload on stdout.
run() { AGENT_SEND_SENDER="$STUB" bash "$TOOL" -S a:src -n dsthost "$@"; }
# Hermetic auto-label runs: TMUX is controlled explicitly so results never
# depend on whether the caller running this suite sits inside tmux.
run_auto() { # models a sender OUTSIDE tmux (no client context)
env -u MOSAIC_AGENT_NAME -u TMUX \
AGENT_SEND_SENDER="$STUB" PATH="$FAKE_BIN:$PATH" \
bash "$TOOL" -n dsthost "$@"
}
run_auto_in_tmux() { # models a sender INSIDE tmux (client context exists)
env -u MOSAIC_AGENT_NAME TMUX=/fake/socket \
AGENT_SEND_SENDER="$STUB" PATH="$FAKE_BIN:$PATH" \
bash "$TOOL" -n dsthost "$@"
}
# Documented consumer grammar — the daemon will mirror exactly this.
GRAMMAR='^\[(\S+) -> (\S+) class=(terminal-log|actionable|human|reaction|digest)\] (.*)$'
GRAMMAR_NOCLASS='^\[(\S+) -> (\S+)\] (.*)$'
# 1. REGRESSION BAR: classic preamble, byte-for-byte.
got=$(run -s mos -m "hello world")
want='[a:src -> dsthost:mos] hello world'
[ "$got" = "$want" ] && ok "regression: no --class is byte-identical" \
|| no "regression: no --class is byte-identical" "got=[$got] want=[$want]"
# 2. --class space form emits the token.
got=$(run -s mos --class terminal-log -m "ACK")
want='[a:src -> dsthost:mos class=terminal-log] ACK'
[ "$got" = "$want" ] && ok "--class terminal-log emits token" \
|| no "--class terminal-log emits token" "got=[$got] want=[$want]"
# 3. --class=value equals form.
got=$(run -s mos --class=actionable -m "decide X")
want='[a:src -> dsthost:mos class=actionable] decide X'
[ "$got" = "$want" ] && ok "--class=actionable (equals form)" \
|| no "--class=actionable (equals form)" "got=[$got] want=[$want]"
# 4. -C short form.
got=$(run -s mos -C human -m "from a person")
want='[a:src -> dsthost:mos class=human] from a person'
[ "$got" = "$want" ] && ok "-C human (short form)" \
|| no "-C human (short form)" "got=[$got] want=[$want]"
# 5. invalid class => exit 3, no send.
if out=$(run -s mos --class bogus -m "x" 2>/dev/null); then
no "invalid class rejected" "expected non-zero exit, got 0 (out=[$out])"
else
rc=$?
[ "$rc" = 3 ] && [ -z "$out" ] && ok "invalid class => exit 3, nothing sent" \
|| no "invalid class => exit 3, nothing sent" "rc=$rc out=[$out]"
fi
# 6. --class with no value => exit 3.
if run -s mos -m "x" --class 2>/dev/null; then
no "--class with no value rejected" "expected non-zero exit, got 0"
else
[ "$?" = 3 ] && ok "--class with no value => exit 3" || no "--class with no value => exit 3" "wrong rc"
fi
# 11. --class digest (space form) is accepted.
got=$(run -s mos --class digest -m "wake payload")
want='[a:src -> dsthost:mos class=digest] wake payload'
if [ "$got" = "$want" ]; then ok "--class digest emits token"
else no "--class digest emits token" "got=[$got] want=[$want]"
fi
# 12. -C digest short form.
got=$(run -s mos -C digest -m "coalesced wake")
want='[a:src -> dsthost:mos class=digest] coalesced wake'
if [ "$got" = "$want" ]; then ok "-C digest (short form)"
else no "-C digest (short form)" "got=[$got] want=[$want]"
fi
# 7. consumer grammar parses every class + classic line.
for c in terminal-log actionable human reaction digest; do
line=$(run -s mos --class "$c" -m "body $c")
[[ "$line" =~ $GRAMMAR ]] && [ "${BASH_REMATCH[3]}" = "$c" ] && [ "${BASH_REMATCH[4]}" = "body $c" ] \
&& ok "grammar parses class=$c" || no "grammar parses class=$c" "line=[$line]"
done
classic=$(run -s mos -m "plain body")
[[ "$classic" =~ $GRAMMAR_NOCLASS ]] && [ "${BASH_REMATCH[3]}" = "plain body" ] \
&& ok "grammar (no-class) parses classic line" || no "grammar (no-class) parses classic line" "line=[$classic]"
# 8. Exported pane identity wins even when dispatch targets another tmux socket.
src_host=$(hostname -s)
got=$(MOSAIC_AGENT_NAME=authoritative-agent FAKE_TMUX_MODE=sessions \
AGENT_SEND_SENDER="$STUB" PATH="$FAKE_BIN:$PATH" \
bash "$TOOL" -L destination-socket -n dsthost -s mos -m "env identity")
want="[$src_host:authoritative-agent -> dsthost:mos] env identity"
[ "$got" = "$want" ] && ok "MOSAIC_AGENT_NAME is authoritative across sockets" \
|| no "MOSAIC_AGENT_NAME is authoritative across sockets" "got=[$got] want=[$want]"
# 9. Without the env identity, self-lookup uses local tmux, not destination -L.
# Sender is INSIDE tmux: the only context where display-message self-lookup
# is safe (it resolves against this client's own session).
got=$(FAKE_TMUX_MODE=sessions run_auto_in_tmux -L destination-socket -s mos -m "local fallback")
want="[$src_host:local-agent -> dsthost:mos] local fallback"
[ "$got" = "$want" ] && ok "cross-socket fallback uses local sender session" \
|| no "cross-socket fallback uses local sender session" "got=[$got] want=[$want]"
[[ "$got" != *":destination-holder ->"* ]] \
&& ok "cross-socket fallback rejects destination holder identity" \
|| no "cross-socket fallback rejects destination holder identity" "got=[$got]"
# 9b. NO tmux context: display-message answers with the LAST-ACTIVE session —
# someone else's identity (forgery vector). The label must be `unverified`,
# never a borrowed name, even though a tmux server exists here and the fake
# would confidently answer `local-agent`.
got=$(FAKE_TMUX_MODE=sessions run_auto -s mos -m "no tmux context")
want="[$src_host:unverified -> dsthost:mos] no tmux context"
[ "$got" = "$want" ] && ok "no-tmux sender labeled unverified, never borrowed" \
|| no "no-tmux sender labeled unverified, never borrowed" "got=[$got] want=[$want]"
# 10. If neither env nor local tmux identifies the sender, preserve '?'.
got=$(FAKE_TMUX_MODE=unavailable run_auto_in_tmux -L destination-socket -s mos -m "unknown fallback")
want="[$src_host:? -> dsthost:mos] unknown fallback"
[ "$got" = "$want" ] && ok "unknown sender falls back to ?" \
|| no "unknown sender falls back to ?" "got=[$got] want=[$want]"
echo "---"
echo "PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ]
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# auto-submit-drafts.sh — watchdog for Claude Code panes that receive channel
# messages but leave them as unsubmitted prompt drafts. Intended for Mos only.
set -uo pipefail
TARGET="${1:-mos-claude}"
INTERVAL="${INTERVAL:-2}"
STABLE_SECONDS="${STABLE_SECONDS:-4}"
LOG_PREFIX="[auto-submit-drafts:$TARGET]"
last_prompt=""
first_seen=0
prompt_text() {
tmux capture-pane -t "$TARGET" -p 2>/dev/null | python3 -c '
import sys, re
lines = sys.stdin.read().splitlines()
idx = None
for i in range(len(lines)-1, -1, -1):
if "" in lines[i]:
idx = i
break
if idx is None:
raise SystemExit
parts = []
after = lines[idx].split("", 1)[1]
parts.append(after)
for line in lines[idx+1:]:
# Stop at Claude Code separator/border lines.
if "─" in line or "╰" in line or "╭" in line:
break
s = line.replace("\u00a0", " ")
s = re.sub(r"[\x00-\x1f\x7f]", "", s).strip()
if s:
parts.append(s)
text = " ".join(parts).replace("\u00a0", " ")
text = re.sub(r"[\x00-\x1f\x7f]", "", text).strip()
print(text)
'
}
while true; do
if ! tmux has-session -t "$TARGET" 2>/dev/null; then
echo "$LOG_PREFIX target missing; waiting" >&2
sleep "$INTERVAL"
last_prompt=""
first_seen=0
continue
fi
current="$(prompt_text || true)"
now="$(date +%s)"
if [[ -z "$current" ]]; then
last_prompt=""
first_seen=0
sleep "$INTERVAL"
continue
fi
if [[ "$current" != "$last_prompt" ]]; then
last_prompt="$current"
first_seen="$now"
sleep "$INTERVAL"
continue
fi
age=$(( now - first_seen ))
if (( age >= STABLE_SECONDS )); then
echo "$LOG_PREFIX submitting stable draft after ${age}s: ${current:0:120}" >&2
tmux send-keys -t "$TARGET" C-j
sleep 0.8
tmux send-keys -t "$TARGET" C-m
sleep 2
last_prompt=""
first_seen=0
else
sleep "$INTERVAL"
fi
done
+220
View File
@@ -0,0 +1,220 @@
#!/usr/bin/env bash
# send-message.sh — reliably deliver a message to a tmux pane running an
# interactive REPL (e.g. a Claude Code / Codex agent).
#
# WHY THIS EXISTS
# Pasting multi-line text into an interactive agent REPL via `tmux send-keys`
# is unreliable: the text lands in the input box but a single trailing Enter
# in the same keystroke stream is frequently swallowed, so the message sits as
# an UNSUBMITTED DRAFT ("Press up to edit queued messages") and the agent never
# sees it. The mechanical fix is: paste as a bracketed paste (so embedded
# newlines don't submit early), pause, then send Enter as its OWN keystroke,
# pause, and send Enter again to flush. An extra Enter on an empty prompt is a
# no-op in Claude Code, so the double-Enter is safe.
#
# USAGE
# send-message.sh [-L socket_name] -t <target> -m "message"
# send-message.sh [-L socket_name] -t <target> -f <file>
# echo "message" | send-message.sh [-L socket_name] -t <target>
# ssh host bash -s -- -L socket -t <target> -b "$(base64 -w0 <<<msg)" < send-message.sh
#
# OPTIONS
# -L NAME tmux socket name passed to `tmux -L NAME` (optional)
# -t TARGET tmux target: session, or session:window.pane [required]
# -m MESSAGE message text (single- or multi-line)
# -f FILE read message from FILE instead of -m
# -b BASE64 message as base64 (ssh-safe transport; decoded internally)
# -r N Enter-flush attempts (default 2)
# -v verbose: print a short tail of the pane after delivery
# -h help
#
# EXIT CODES
# 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 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.
# 3 usage error
set -uo pipefail
SOCKET_NAME=""; TARGET=""; MSG=""; FILE=""; B64=""; RETRIES=2; VERBOSE=0
usage() { sed -n '2,34p' "$0"; exit "${1:-3}"; }
while getopts "L:t:m:f:b:r:vh" o; do
case "$o" in
L) SOCKET_NAME=$OPTARG ;;
t) TARGET=$OPTARG ;; m) MSG=$OPTARG ;; f) FILE=$OPTARG ;; b) B64=$OPTARG ;;
r) RETRIES=$OPTARG ;; v) VERBOSE=1 ;; h) usage 0 ;; *) usage 3 ;;
esac
done
[ -n "$TARGET" ] || { echo "ERROR: -t TARGET is required" >&2; usage 3; }
if [ -n "$B64" ]; then MSG=$(printf '%s' "$B64" | base64 -d) || { echo "ERROR: bad -b base64" >&2; exit 3; }
elif [ -n "$FILE" ]; then [ -r "$FILE" ] || { echo "ERROR: cannot read $FILE" >&2; exit 3; }; MSG=$(cat -- "$FILE")
elif [ -z "$MSG" ] && [ ! -t 0 ]; then MSG=$(cat)
fi
[ -n "$MSG" ] || { echo "ERROR: empty message (use -m, -f, or stdin)" >&2; exit 3; }
tmux_cmd=(tmux)
if [ -n "$SOCKET_NAME" ]; then
tmux_cmd+=(-L "$SOCKET_NAME")
fi
# tmux accepts `=session` for some commands, but pane-level commands such as
# capture-pane require a pane-qualified target. Keep exact-session addressing
# convenient while avoiding accidental prefix matches.
EFFECTIVE_TARGET=$TARGET
if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then
EFFECTIVE_TARGET="${TARGET}:0.0"
fi
# Target must resolve to a live pane.
if ! "${tmux_cmd[@]}" list-panes -t "$EFFECTIVE_TARGET" >/dev/null 2>&1; then
echo "ERROR: tmux target not found: $TARGET" >&2; exit 1
fi
QUEUED_RE='Press up to edit queued messages'
# A distinctive tail of the message to spot an unsubmitted draft on the input line.
snippet=$(printf '%s' "$MSG" | tr '\n' ' ' | tr -s ' ' | sed 's/[^[:print:]]//g' | tail -c 32)
# 1) Paste the body as a bracketed paste so multi-line content does not submit
# line-by-line. load-buffer/paste-buffer is far safer than `send-keys -l`.
# Buffer name MUST be unique per invocation: concurrent senders on the shared
# tmux server race a fixed name (load overwrites load, -d deletes underneath),
# cross-delivering or dropping messages — bit the fleet on the 2026-07-09
# simultaneous restart (briefs swapped between sessions).
BUF="__mosaic_send_$$_$(date +%s%N)"
printf '%s' "$MSG" | "${tmux_cmd[@]}" load-buffer -b "$BUF" -
# -p = bracketed paste when the client supports it; fall back if not.
"${tmux_cmd[@]}" paste-buffer -d -p -b "$BUF" -t "$EFFECTIVE_TARGET" 2>/dev/null \
|| "${tmux_cmd[@]}" paste-buffer -d -b "$BUF" -t "$EFFECTIVE_TARGET" \
|| "${tmux_cmd[@]}" delete-buffer -b "$BUF" 2>/dev/null
# ^ -d deletes the buffer only on a SUCCESSFUL paste; if both attempts fail
# (e.g. the target vanished since the liveness check), delete explicitly —
# named buffers are exempt from tmux's buffer-limit eviction, so orphans
# would otherwise accumulate forever.
sleep 0.5
# 2) Submit, then POSITIVELY confirm submission by DRAFT TRANSITION, not by prompt
# glyph. The historical bug was treating ABSENCE of a draft as delivery; the
# 2026-08 fix over-corrected to glyph inference (grep '|^>|│ >'), which locates
# only Claude Code's box and false-NEGATIVES every glyphless REPL (pi renders a
# U+2500 rule, no glyph) — a delivered message reported "UNDELIVERED", driving a
# retry that duplicates it. Runtime-agnostic evidence: our message tail sits on
# the INPUT line (located by the cursor row, not a glyph) BEFORE Enter, and has
# LEFT it AFTER — that transition is positive proof of submission and needs no
# glyph. Absence alone still never means delivered: if we never saw our draft on
# the input line we stay UNCONFIRMED (wrong/dead pane), and a draft that never
# leaves the input line stays a DRAFT (exit 2), preserving both historical guards.
_cursor_line() { # echo the pane's current input (cursor) line, glyph-free
local cy line
cy=$("${tmux_cmd[@]}" display-message -p -t "$EFFECTIVE_TARGET" -F '#{cursor_y}' 2>/dev/null) || return 1
[ -n "$cy" ] || return 1
"${tmux_cmd[@]}" capture-pane -t "$EFFECTIVE_TARGET" -p 2>/dev/null | sed -n "$((cy + 1))p"
}
_draft_on_input() { # true iff our message tail is sitting on the input line now
[ -n "$snippet" ] || return 1
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.
saw_draft=0
_draft_on_input && saw_draft=1
status="unconfirmed"
for attempt in $(seq 1 $((RETRIES + 1))); do
"${tmux_cmd[@]}" send-keys -t "$EFFECTIVE_TARGET" Enter
sleep 1.2
pane=$("${tmux_cmd[@]}" capture-pane -t "$EFFECTIVE_TARGET" -p 2>/dev/null)
if grep -qF "$QUEUED_RE" <<<"$pane"; then
status="queued"; break
fi
# 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
status="draft"; continue # still on the input line => not submitted; flush + retry
fi
status="delivered"; break # left the input line => positively submitted
fi
# No confirmed baseline yet: try to (re)acquire it; never infer delivery from absence.
if _draft_on_input; then saw_draft=1; status="draft"; continue; fi
status="unconfirmed"; continue
done
[ "$VERBOSE" = 1 ] && { echo "--- pane tail ($TARGET) ---"; printf '%s\n' "$pane" | tail -4; echo "---"; }
case "$status" in
delivered) echo "✓ delivered to $TARGET"; exit 0 ;;
queued) echo "✓ queued to $TARGET (agent busy — will process when it returns to prompt)"; exit 0 ;;
draft) echo "✗ still an unsubmitted draft on $TARGET after $RETRIES flush attempts" >&2; exit 2 ;;
unconfirmed) echo "✗ could not confirm submission on $TARGET: REPL input prompt not locatable after $((RETRIES + 1)) attempts — message may be UNDELIVERED (check target/pane, retry, or escalate)" >&2; exit 2 ;;
*) echo "✗ could not confirm submission on $TARGET (unexpected state '$status')" >&2; exit 2 ;;
esac
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Red-first regression test for E7 (#1017 task 2): the confirm-check must bind
# "delivered" to WHETHER THE MESSAGE WAS SUBMITTED, not to which runtime's prompt
# glyph is present. A pi seat renders a U+2500 rule input box with no /^>/│ >
# glyph; send-message.sh:118 locates the box only by glyph, so a genuinely
# delivered message on a glyphless REPL falsely reports exit 2 "may be UNDELIVERED",
# and the operator's rc=2-driven retry duplicates it.
#
# Parameterized on $SEND: RED against the shipping blob (B and D fail), GREEN
# against a candidate patch. No pi; no fake HOME; hermetic throwaway socket.
#
# Submission counting is EXACT and terminal-echo-independent: the fixture message
# is `echo <tok> >>SINK`; each real submission appends one line. wc -l SINK ==
# number of times the REPL actually executed the send. This does not depend on how
# many times the marker string is painted on screen.
set -u
SEND="${SEND:?set SEND=/path/to/send-message.sh}"
SOCKET="glyphagnostic-$$"
TMP="$(mktemp -d)"
tmux() { command tmux -L "$SOCKET" "$@"; }
cleanup() { command tmux -L "$SOCKET" kill-server 2>/dev/null; rm -rf "$TMP"; }
trap cleanup EXIT
pass=0; fail=0
ok() { printf 'ok %s\n' "$1"; pass=$((pass+1)); }
no() { printf 'FAIL %s -- %s\n' "$1" "$2"; fail=$((fail+1)); }
mk() { tmux new-session -d -s "$1" -x 120 -y 40 -c "$TMP" "PS1='$2' exec bash --noprofile --norc -i"; sleep 0.5; }
subs() { [ -f "$1" ] && wc -l <"$1" | tr -d ' ' || echo 0; } # exact submission count
echo "SEND=$SEND tmux $(command tmux -V | awk '{print $2}')"
# --- A (control): glyph box () that submits => exit 0, exactly one submission.
mk ctl ' '
SINK="$TMP/sink.ctl"
out=$("$SEND" -L "$SOCKET" -t ctl -m "echo x >>'$SINK'" 2>"$TMP/e.ctl"); rc=$?; sleep 0.4
if [ "$rc" = 0 ] && [ "$(subs "$SINK")" = 1 ]; then
ok "control: -box submits => exit 0, exactly one submission"
else no "control: -box submits => exit 0, one submission" "rc=$rc subs=$(subs "$SINK") err=[$(cat "$TMP/e.ctl")]"; fi
# --- B (THE false-rc regression): glyphless U+2500 box that SUBMITS. Message lands
# (subs==1) yet shipping reports exit 2. Must be exit 0.
mk sub $'──────── \n'
SINK="$TMP/sink.sub"
out=$("$SEND" -L "$SOCKET" -t sub -m "echo x >>'$SINK'" 2>"$TMP/e.sub"); rc=$?; sleep 0.4
if [ "$rc" = 0 ] && [ "$(subs "$SINK")" = 1 ]; then
ok "glyphless: U+2500 box that submits => exit 0 (delivered, not 'UNDELIVERED')"
else no "glyphless: U+2500 box that submits => exit 0" \
"rc=$rc subs=$(subs "$SINK")(delivered=$([ "$(subs "$SINK")" -ge 1 ] && echo yes||echo no)) err=[$(cat "$TMP/e.sub")]"; fi
# --- D (duplicate arm): operator follows the rc=2 stderr and retries once. On the
# glyphless box, shipping => two submissions (the reported duplicate). The
# property: one logical send => exactly one submission. Same fix closes it.
mk dup $'──────── \n'
SINK="$TMP/sink.dup"
tries=0
for attempt in 1 2; do
tries=$((tries+1))
out=$("$SEND" -L "$SOCKET" -t dup -m "echo x >>'$SINK'" 2>/dev/null); rc=$?
sleep 0.4
[ "$rc" = 0 ] && break # operator stops retrying only when told delivered
done
if [ "$(subs "$SINK")" = 1 ]; then
ok "duplicate: one logical send (rc-driven retry) => exactly one submission (tries=$tries)"
else no "duplicate: one logical send => exactly one submission" "submissions=$(subs "$SINK") tries=$tries"; fi
# --- E (faithful hung managed TUI, NOT a cooked shell): raw/no-echo, paints nothing.
# A cooked `sleep infinity` echoes the paste via the kernel line discipline and
# false-passes a cursor-row fix that is correct on real seats (measured). So: raw.
mk_rawstuck() { tmux new-session -d -s "$1" -x 120 -y 40 -c "$TMP" \
"bash --noprofile --norc -c 'stty -echo -icanon min 1 time 0 2>/dev/null; exec sleep infinity'"; sleep 0.5; }
mk_rawstuck estuck
SINK="$TMP/sink.estuck"
out=$("$SEND" -L "$SOCKET" -t estuck -r 1 -m "this stuck draft was never submitted" 2>/dev/null); rc=$?
sleep 0.3
if [ "$rc" != 0 ] && [ "$(subs "$SINK")" = 0 ]; then
ok "raw/no-echo stuck TUI (not submitted) => non-zero (no false delivered)"
else no "raw stuck TUI must NOT report delivered" "rc=$rc subs=$(subs "$SINK")"; fi
# --- F (busy/queued branch, your BUSY-not-runtime finding): glyphless pane rendering the
# queued banner, never consuming. QUEUED_RE :113 fires before the glyph grep => rc=0.
mk_busy() { tmux new-session -d -s "$1" -x 120 -y 40 -c "$TMP" \
"bash --noprofile --norc -c 'printf \"Press up to edit queued messages\n\"; exec sleep infinity'"; sleep 0.5; }
mk_busy ebusy
SINK="$TMP/sink.ebusy"
out=$("$SEND" -L "$SOCKET" -t ebusy -m "echo x >>'$SINK'" 2>/dev/null); rc=$?; sleep 0.3
if [ "$rc" = 0 ]; then
ok "busy/queued-banner glyphless => exit 0 (queued is delivery; runtime owns custody)"
else no "busy/queued-banner must report delivered" "rc=$rc"; fi
# --- C (historical-bug guard): unresolvable target. No pane ever carried our draft
# => must fail, never infer delivered from absence of a glyph/snippet.
if out=$("$SEND" -L "$SOCKET" -t "nonexistent-$$" -m "echo x >>'$TMP/sink.wrong'" 2>/dev/null); then
no "wrong-pane: unresolvable target must NOT report success" "expected non-zero, got 0"
else ok "wrong-pane: unresolvable target => non-zero (no false delivered)"; fi
echo "---"; echo "pass=$pass fail=$fail"
[ "$fail" = 0 ]
@@ -0,0 +1,169 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
SEND_MESSAGE="$SCRIPT_DIR/send-message.sh"
AGENT_SEND="$SCRIPT_DIR/agent-send.sh"
SOCKET="mosaic-test-$RANDOM-$$"
TARGET="target-$RANDOM"
DEFAULT_TARGET="default-target-$RANDOM"
TMPDIR=$(mktemp -d)
ART_OUT=$(mktemp)
AMB_OUT=$(mktemp)
AMB_ERR=$(mktemp)
A2_OUT=$(mktemp)
A2_ERR=$(mktemp)
UNIQ_OUT=$(mktemp)
UNIQ_ERR=$(mktemp)
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" $ART_OUT $AMB_OUT $AMB_ERR $A2_OUT $A2_ERR $UNIQ_OUT $UNIQ_ERR' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
require_tmux() {
command -v tmux >/dev/null 2>&1 || fail "tmux is required"
}
capture_named() {
tmux -L "$SOCKET" capture-pane -t "=$TARGET:0.0" -p
}
capture_default() {
tmux capture-pane -t "=$DEFAULT_TARGET:0.0" -p
}
require_tmux
tmux -L "$SOCKET" new-session -d -s "$TARGET" -c "$TMPDIR" 'PS1=" " exec bash --noprofile --norc -i'
tmux new-session -d -s "$DEFAULT_TARGET" -c "$TMPDIR" 'PS1=" " exec bash --noprofile --norc -i'
"$SEND_MESSAGE" -L "$SOCKET" -t "=$TARGET" -m "named socket hello" >/tmp/send-message-named.out
sleep 0.2
named_pane="$(capture_named)" || fail "could not capture named socket pane"
grep -qF "named socket hello" <<<"$named_pane" || fail "send-message.sh did not deliver to named socket"
default_pane="$(capture_default)" || fail "could not capture default socket pane"
if grep -qF "named socket hello" <<<"$default_pane"; then
fail "send-message.sh leaked named-socket message to default tmux server"
fi
"$AGENT_SEND" -L "$SOCKET" -S "tester:source" -s "=$TARGET" -m "agent socket hello" >/tmp/agent-send-named.out
sleep 0.2
named_pane="$(capture_named)" || fail "could not capture named socket pane"
grep -qF "[tester:source ->" <<<"$named_pane" || fail "agent-send.sh did not include preamble"
grep -qF "agent socket hello" <<<"$named_pane" || fail "agent-send.sh did not deliver to named socket"
default_pane="$(capture_default)" || fail "could not capture default socket pane"
if grep -qF "agent socket hello" <<<"$default_pane"; then
fail "agent-send.sh leaked named-socket message to default tmux server"
fi
# Concurrency: parallel senders on one server must not cross-deliver or drop.
# Locks the unique-per-invocation paste buffer (a fixed buffer name raced:
# load overwrote load, -d deleted underneath — messages swapped between panes).
CONC_N=5
for i in $(seq 1 "$CONC_N"); do
tmux -L "$SOCKET" new-session -d -s "conc-$i" -c "$TMPDIR" 'PS1=" " exec bash --noprofile --norc -i'
done
pids=()
for i in $(seq 1 "$CONC_N"); do
"$SEND_MESSAGE" -L "$SOCKET" -t "=conc-$i" -m "CONCPAYLOAD-${i}-END" >/dev/null &
pids+=($!)
done
for pid in "${pids[@]}"; do
wait "$pid" || fail "concurrent send-message.sh invocation exited non-zero"
done
sleep 0.2
for i in $(seq 1 "$CONC_N"); do
pane=$(tmux -L "$SOCKET" capture-pane -t "=conc-$i:0.0" -p)
grep -qF "CONCPAYLOAD-${i}-END" <<<"$pane" \
|| fail "concurrent send dropped payload for pane conc-$i"
for j in $(seq 1 "$CONC_N"); do
[ "$j" = "$i" ] && continue
if grep -qF "CONCPAYLOAD-${j}-END" <<<"$pane"; then
fail "concurrent send cross-delivered payload $j to pane conc-$i"
fi
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
env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN" -m "must refuse" >$AMB_OUT 2>$AMB_ERR || amb_rc=$?
[ "$amb_rc" -eq 4 ] || fail "ambiguity refusal: rc=$amb_rc want 4 (stderr: $(cat $AMB_ERR))"
grep -q "multiple sockets" $AMB_ERR || fail "ambiguity refusal message missing socket list"
grep -qF "$SOCKET" $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 A2: with MOSAIC_TMUX_SOCKET exported, a twin session is NOT ambiguous:
# the env var disambiguates by precedence (codex PR #1466 blocker).
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'
a2_rc=0
MOSAIC_TMUX_SOCKET="$SOCKET" "$AGENT_SEND" -s "$TWIN" -m "env disambiguated" >$A2_OUT 2>$A2_ERR || a2_rc=$?
[ "$a2_rc" -eq 0 ] || fail "env disambiguation: rc=$a2_rc (stderr: $(cat $A2_ERR))"
sleep 0.2
a2_pane="$(tmux -L "$SOCKET" capture-pane -t "=$TWIN:0.0" -p)" || fail "cannot capture twin (arm A2)"
grep -qF "env disambiguated" <<<"$a2_pane" || fail "env disambiguation did not deliver on the named socket"
a2_default="$(tmux capture-pane -t "=$TWIN:0.0" -p)" || true
if grep -qF "env disambiguated" <<<"$a2_default"; then
fail "env disambiguation cross-delivered to the default-socket twin"
fi
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.
# Arm A3: prefix matching must not produce false socket hits (codex PR
# #1466): a session named TWIN-old must not count as a hit for target
# TWIN (tmux target syntax prefix-matches without '=').
PSEUDO="${TWIN}-old"
tmux new-session -d -s "$PSEUDO" -c "$TMPDIR" 'PS1=" " exec bash --noprofile --norc -i'
A3_ERR=$(mktemp)
a3_rc=0
env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN" -m "prefix trap" >/dev/null 2>"$A3_ERR" || a3_rc=$?
# TWIN exists nowhere (both twins killed after arm A2); with '=' the
# PSEUDO session is not a hit, so the sender must fail target-not-found
# (rc 1) instead of delivering into the prefix-named session.
[ "$a3_rc" -eq 1 ] || fail "prefix false-hit: rc=$a3_rc want 1 (stderr: $(cat "$A3_ERR"))"
a3_pane="$(tmux capture-pane -t "=$PSEUDO:0.0" -p 2>/dev/null)" || a3_pane=""
if printf '%s' "$a3_pane" | grep -F "prefix trap" >/dev/null; then
fail "delivery landed in the prefix-named session (false socket hit)"
fi
tmux kill-session -t "$PSEUDO" >/dev/null 2>&1 || true
rm -f "$A3_ERR"
# Arm A4: compound targets pin the SESSION component exact (codex PR
# #1466): 'TWIN:0.0' must not resolve into the prefix-named session.
PSEUDO2="${TWIN}-old"
tmux new-session -d -s "$PSEUDO2" -c "$TMPDIR" 'PS1=" " exec bash --noprofile --norc -i'
A4_ERR=$(mktemp)
a4_rc=0
env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TWIN:0.0" -m "compound trap" >/dev/null 2>"$A4_ERR" || a4_rc=$?
[ "$a4_rc" -eq 1 ] || fail "compound prefix false-hit: rc=$a4_rc want 1 (stderr: $(cat "$A4_ERR"))"
a4_pane="$(tmux capture-pane -t "=$PSEUDO2:0.0" -p 2>/dev/null)" || a4_pane=""
if printf '%s' "$a4_pane" | grep -F "compound trap" >/dev/null; then
fail "compound delivery landed in the prefix-named session"
fi
tmux kill-session -t "$PSEUDO2" >/dev/null 2>&1 || true
rm -f "$A4_ERR"
uniq_rc=0
env -u MOSAIC_TMUX_SOCKET "$AGENT_SEND" -s "$TARGET" -m "autoresolved hello" >$UNIQ_OUT 2>$UNIQ_ERR || uniq_rc=$?
[ "$uniq_rc" -eq 0 ] || fail "unique auto-resolution: rc=$uniq_rc (stderr: $(cat $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"
@@ -0,0 +1,210 @@
#!/usr/bin/env bash
# test-send-message-verdict.sh — locks the fail-loud verdict logic of the patched
# send-message.sh against three real tmux-pane fixtures on a throwaway socket:
#
# 1. DELIVERED — a REPL that renders a ` ` input box and submits on Enter
# (text scrolls to history, box clears) => exit 0 "✓ delivered".
# 2. DELIVERED — a pane with NO prompt glyph that DOES submit => exit 0. A pi
# seat is this fixture (U+2500 rule, no glyph). Reshaped for
# #1257; see the note at the fixture for why the old exit-2
# assertion was wrong.
# 2b. UNCONFIRMED— a glyphless pane that never submits (raw/no-echo hung TUI)
# => must fail loud. This carries the historical
# false-positive guard that fixture 2 used to be credited with.
# 3. DRAFT — a ` `-prompt pane that never submits (message stays on the
# input line) => exit 2, stderr "unsubmitted draft".
# 4. DELIVERED — a pane whose input box is two `─` rules with NO prompt glyph
# (box shape) anywhere (pi's shape) and which submits => exit 0. Pre-#1362
# the glyph probe could not see this box at all, so EVERY send
# to such a pane reported "may be UNDELIVERED" while landing.
# 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)
SEND="${SEND:-$HERE/send-message.sh}"
SOCKET="verdict-test-$RANDOM-$$"
TMP=$(mktemp -d)
trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); printf ' ok %s\n' "$1"; }
no() { FAIL=$((FAIL+1)); printf ' FAIL %s\n %s\n' "$1" "$2"; }
command -v tmux >/dev/null 2>&1 || { echo "tmux required" >&2; exit 1; }
# --- Fixture 1: a submitting REPL with a prompt box (interactive bash, glyph PS1).
# readline strips bracketed-paste markers just like a real agent REPL; Enter
# executes (text -> scrollback), leaving a fresh empty ` ` box.
tmux -L "$SOCKET" new-session -d -s repl -c "$TMP" \
'PS1=" " exec bash --noprofile --norc -i'
sleep 0.3
out=$("$SEND" -L "$SOCKET" -t "=repl" -m "verdict fixture one delivered ok" 2>"$TMP/e1"); rc=$?
if [ "$rc" -eq 0 ] && grep -qF "✓ delivered" <<<"$out"; then
ok "delivered: -prompt REPL that submits => exit 0 ✓ delivered"
else
no "delivered: -prompt REPL that submits => exit 0 ✓ delivered" "rc=$rc out=[$out] err=[$(cat "$TMP/e1")]"
fi
# --- Fixture 2: NO prompt glyph, and the pane DOES submit (interactive bash).
# RESHAPED 2026-08-16 (#1257), deliberately. This fixture previously asserted
# exit 2 here and was labelled "false-positive FIXED". That assertion was wrong,
# and locking it in is what kept E7 alive: the pane submits, so "delivered" is
# the truth, and a pi seat — whose input box is a bare U+2500 rule with no glyph
# — IS this fixture. Reporting exit 2 for it told operators a delivered message
# may be undelivered, and the retry that advice invites is the duplicate.
#
# The guard this fixture was reaching for is real and is NOT dropped: "never
# infer delivered from absence" is now enforced positively by fixture 2b below
# (glyphless AND not submitting => must fail) and by fixture 3 (locatable box
# still carrying our tail => draft). Absence alone decides nothing either way.
tmux -L "$SOCKET" new-session -d -s noglyph -c "$TMP" \
'PS1="sh-noglyph$ " exec bash --noprofile --norc -i'
sleep 0.3
out=$("$SEND" -L "$SOCKET" -t "=noglyph" -m "verdict fixture two must fail loud" 2>"$TMP/e2"); rc=$?
if [ "$rc" -eq 0 ] && grep -qF "✓ delivered" <<<"$out"; then
ok "delivered: glyphless pane that submits => exit 0 (runtime-agnostic, E7 FIXED)"
else
no "delivered: glyphless pane that submits => exit 0" "rc=$rc out=[$out] err=[$(cat "$TMP/e2")]"
fi
# --- Fixture 2b: NO prompt glyph AND never submits — a hung managed TUI holding the
# terminal in raw/no-echo, which is what a stuck agent seat actually is (measured
# on live pi: stty -echo -icanon). Nothing is echoed, nothing is consumed, so
# there is no positive evidence of submission and the tool MUST fail loud. This
# is the historical false-positive guard, kept as a positive test.
tmux -L "$SOCKET" new-session -d -s rawstuck -c "$TMP" \
'bash --noprofile --norc -c "stty -echo -icanon min 1 time 0 2>/dev/null; exec sleep infinity"'
sleep 0.3
if out=$("$SEND" -L "$SOCKET" -t "=rawstuck" -r 1 -m "verdict fixture two-b never submitted" 2>"$TMP/e2b"); then
no "unconfirmed: glyphless hung TUI must NOT report success" "expected non-zero, got 0 (out=[$out])"
else
rc=$?
if [ "$rc" -ne 0 ] && grep -qF "could not confirm submission" "$TMP/e2b"; then
ok "unconfirmed: glyphless hung TUI (raw/no-echo) => non-zero + 'could not confirm submission'"
else
no "unconfirmed: glyphless hung TUI => non-zero + stderr" "rc=$rc err=[$(cat "$TMP/e2b")]"
fi
fi
# --- Fixture 3: a box that never submits (sleep ignores stdin; TTY echo keeps the
# pasted tail sitting on the line) => draft => exit 2.
tmux -L "$SOCKET" new-session -d -s draft -c "$TMP" \
'printf " "; exec sleep infinity'
sleep 0.3
if out=$("$SEND" -L "$SOCKET" -t "=draft" -r 1 -m "verdict fixture three stuck unsubmitted draft" 2>"$TMP/e3"); then
no "draft: unsubmitted message must NOT report success" "expected exit 2, got 0 (out=[$out])"
else
rc=$?
if [ "$rc" -eq 2 ] && grep -qF "unsubmitted draft" "$TMP/e3"; then
ok "draft: stuck -line message => exit 2 + 'unsubmitted draft'"
else
no "draft: stuck -line message => exit 2 + stderr" "rc=$rc err=[$(cat "$TMP/e3")]"
fi
fi
# --- Fixtures 4 and 5: a pi-shaped pane. The input box is two `─` rules with the
# text between them and NO prompt glyph anywhere, so the glyph probe alone can
# never locate it and every send reports "may be UNDELIVERED" (#1362). The
# renderer below is the shape, not the runtime: MODE=clear submits (box empties),
# MODE=keep leaves the text sitting in the box.
cat > "$TMP/pibox.sh" <<'PIBOX'
#!/usr/bin/env bash
MODE=${1:-clear}
RULE=$(printf '─%.0s' $(seq 1 60))
buf=""
draw() {
printf '\033[H\033[2J'
printf 'fixture output line\n\n'
printf '%s\n' "$RULE"
printf '%s\n' "$buf"
printf '%s\n' "$RULE"
printf '~/fixture (main)\n'
printf 'tok 0 model fixture\n'
}
draw
while IFS= read -r line; do
# keep: hold the tail across every flush Enter, which is what a stuck draft does.
if [ "$MODE" = keep ]; then [ -n "$line" ] && buf=$line; else buf=""; fi
draw
done
PIBOX
chmod +x "$TMP/pibox.sh"
tmux -L "$SOCKET" new-session -d -s pibox -c "$TMP" "exec bash '$TMP/pibox.sh' clear"
sleep 0.3
out=$("$SEND" -L "$SOCKET" -t "=pibox" -m "pi fixture four delivered ok" 2>"$TMP/e4"); rc=$?
if [ "$rc" -eq 0 ] && grep -qF "✓ delivered" <<<"$out"; then
ok "delivered: glyphless box-drawn REPL that submits => exit 0 ✓ delivered"
else
no "delivered: glyphless box-drawn REPL that submits => exit 0 ✓ delivered" "rc=$rc out=[$out] err=[$(cat "$TMP/e4")]"
fi
tmux -L "$SOCKET" new-session -d -s piboxdraft -c "$TMP" "exec bash '$TMP/pibox.sh' keep"
sleep 0.3
if out=$("$SEND" -L "$SOCKET" -t "=piboxdraft" -r 1 -m "pi fixture five stuck in the box" 2>"$TMP/e5"); then
no "draft: glyphless box-drawn pane holding our tail must NOT report success" "expected exit 2, got 0 (out=[$out])"
else
rc=$?
if [ "$rc" -eq 2 ] && grep -qF "unsubmitted draft" "$TMP/e5"; then
ok "draft: message left in a glyphless box => exit 2 + 'unsubmitted draft'"
else
no "draft: message left in a glyphless box => exit 2 + stderr" "rc=$rc err=[$(cat "$TMP/e5")]"
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 ]