Compare commits

..

2 Commits

Author SHA1 Message Date
Hermes Agent
4b50cc93d1 fix(tmux): preserve sender identity across sockets
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Prefer the pane's MOSAIC_AGENT_NAME and query only the local tmux server when falling back, so destination socket holders cannot corrupt reply routing. Add red-first regression coverage for env, local-session, and unknown sender paths.

Co-Authored-By: OpenAI GPT <noreply@openai.com>
2026-07-16 17:30:56 -05:00
3be443c96d feat(mosaic): claudex isolated config + env-inject + yolo wiring (P2–P4 of #790) (#806)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 22:10:33 +00:00
3 changed files with 93 additions and 7 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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 ]