From 4f29cc604d69a6d624734e7f828552e805ebbed5 Mon Sep 17 00:00:00 2001 From: "jason.woltje" Date: Thu, 16 Jul 2026 22:47:25 +0000 Subject: [PATCH] fix(tmux): correct cross-socket sender identity (#808) (#809) --- .../808-agent-send-sender-identity.md | 37 +++++++++++++ .../mosaic/framework/tools/tmux/agent-send.sh | 9 ++-- .../framework/tools/tmux/agent-send.test.sh | 54 ++++++++++++++++++- 3 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 docs/scratchpads/808-agent-send-sender-identity.md diff --git a/docs/scratchpads/808-agent-send-sender-identity.md b/docs/scratchpads/808-agent-send-sender-identity.md new file mode 100644 index 0000000..04b8beb --- /dev/null +++ b/docs/scratchpads/808-agent-send-sender-identity.md @@ -0,0 +1,37 @@ +# Issue #808 — agent-send sender identity + +## Objective + +Fix cross-socket `agent-send.sh` preambles so replies route to the real sender rather than a destination-socket holder session. + +## Scope and acceptance criteria + +- Prefer exported `MOSAIC_AGENT_NAME` as the authoritative sender session name. +- If it is unset, query the sender's local/default tmux socket for `#S` without destination `-L` arguments. +- Preserve `?` when sender identity cannot be determined. +- Do not alter destination socket dispatch. +- Add red-first regressions for all three identity paths. + +## Plan + +1. Extend `agent-send.test.sh` with deterministic fake-tmux coverage. +2. Run the test against the unpatched implementation and record RED evidence. +3. Apply the minimal sender lookup fix only. +4. Run the focused suite and repository quality gates. +5. Commit, queue-guard, push, and open an author-only PR for independent review. + +## Constraints and risks + +- Worker lane is author-only: no self-review or merge. +- `docs/TASKS.md` and mission state are orchestrator-owned and will not be modified. +- Pre-existing runtime changes under `.mosaic/orchestrator/` are excluded from this work. +- Budget: no explicit token cap; keep changes limited to the shell tool, sibling regression test, and this scratchpad. + +## Evidence + +- RED: `bash packages/mosaic/framework/tools/tmux/agent-send.test.sh` failed on the unpatched implementation with `PASS=12 FAIL=3`; it selected `destination-holder` instead of both `MOSAIC_AGENT_NAME=authoritative-agent` and local session `local-agent`. The genuinely unavailable sender case already exercised and preserved `?`. +- GREEN: `bash packages/mosaic/framework/tools/tmux/agent-send.test.sh` passed with `PASS=15 FAIL=0`; coverage includes env authority, local/default tmux fallback across a destination `-L`, explicit rejection of the destination holder, and `?` fallback. +- Syntax: `bash -n packages/mosaic/framework/tools/tmux/agent-send.sh packages/mosaic/framework/tools/tmux/agent-send.test.sh` passed. +- Quality gates: `pnpm typecheck` (42/42 tasks), `pnpm lint` (23/23 tasks), and `pnpm format:check` all passed after installing the frozen lockfile dependencies. The first install attempt failed because pnpm's configured store pointed at `/root`; retrying with the existing user-owned store (`--store-dir /home/hermes/.local/share/pnpm/store`) succeeded without changing tracked dependency files. +- Documentation: no public API or operator workflow changed; the source comment, regression-test contract, and this implementation record cover the internal bug fix. +- Independent review: intentionally pending for the reviewer assigned by `mosaic-100`; this author-only lane will not self-review or merge. diff --git a/packages/mosaic/framework/tools/tmux/agent-send.sh b/packages/mosaic/framework/tools/tmux/agent-send.sh index 4810fc2..9809bf7 100755 --- a/packages/mosaic/framework/tools/tmux/agent-send.sh +++ b/packages/mosaic/framework/tools/tmux/agent-send.sh @@ -122,12 +122,11 @@ fi # Source label: this agent's host:session (auto-detected, overridable). if [ -z "$SRC_LABEL" ]; then - tmux_cmd=(tmux) - if [ -n "$SOCKET_NAME" ]; then - tmux_cmd+=(-L "$SOCKET_NAME") - fi src_host=$(hostname -s 2>/dev/null || echo "?") - src_sess=$("${tmux_cmd[@]}" display-message -p '#S' 2>/dev/null || echo "?") + src_sess=${MOSAIC_AGENT_NAME:-} + if [ -z "$src_sess" ]; then + src_sess=$(tmux display-message -p '#S' 2>/dev/null || echo "?") + fi SRC_LABEL="${src_host}:${src_sess}" fi diff --git a/packages/mosaic/framework/tools/tmux/agent-send.test.sh b/packages/mosaic/framework/tools/tmux/agent-send.test.sh index e02cbc9..bc94e5f 100755 --- a/packages/mosaic/framework/tools/tmux/agent-send.test.sh +++ b/packages/mosaic/framework/tools/tmux/agent-send.test.sh @@ -14,6 +14,9 @@ # 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 "?". set -uo pipefail HERE=$(cd -- "$(dirname -- "$0")" && pwd) @@ -21,22 +24,45 @@ TOOL="$HERE/agent-send.sh" # Capture stub: stands in for send-message.sh. Decodes -b and prints the payload. STUB=$(mktemp) -trap 'rm -f "$STUB"' EXIT +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 "t:b:r:v" o; do case "$o" in b) b64=$OPTARG ;; *) : ;; esac; done +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 "$@"; } +run_auto() { + env -u MOSAIC_AGENT_NAME \ + 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)\] (.*)$' @@ -92,6 +118,30 @@ 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. +got=$(FAKE_TMUX_MODE=sessions run_auto -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]" + +# 10. If neither env nor local tmux identifies the sender, preserve '?'. +got=$(FAKE_TMUX_MODE=unavailable run_auto -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 ]