#!/usr/bin/env bash set -euo pipefail AGENT_NAME=${1:-${MOSAIC_AGENT_NAME:-}} MOSAIC_TMUX_SOCKET=${MOSAIC_TMUX_SOCKET:-mosaic-factory} MOSAIC_AGENT_RUNTIME=${MOSAIC_AGENT_RUNTIME:-pi} MOSAIC_AGENT_WORKDIR=${MOSAIC_AGENT_WORKDIR:-$HOME} MOSAIC_AGENT_COMMAND=${MOSAIC_AGENT_COMMAND:-} if [ -z "$AGENT_NAME" ]; then echo "ERROR: agent name argument or MOSAIC_AGENT_NAME is required" >&2 exit 64 fi if ! command -v tmux >/dev/null 2>&1; then echo "ERROR: tmux is required" >&2 exit 69 fi if tmux -L "$MOSAIC_TMUX_SOCKET" has-session -t "=${AGENT_NAME}:0.0" 2>/dev/null; then echo "Mosaic agent session already running: $AGENT_NAME on socket $MOSAIC_TMUX_SOCKET" exit 0 fi if [ -z "$MOSAIC_AGENT_COMMAND" ]; then MOSAIC_AGENT_COMMAND="mosaic yolo $MOSAIC_AGENT_RUNTIME" fi # ── Derive a runtime-bin PATH prefix ───────────────────────────────────────── # Precedence: # 1. $MOSAIC_RUNTIME_BIN (explicit override) # 2. $(npm config get prefix)/bin (if npm is on PATH) # 3. Fallbacks: $HOME/.npm-global/bin and $HOME/.local/bin # # Only directories that already exist are included. The prefix is baked into # the pane command regardless of what the LAUNCHER process's $PATH contains, # because the tmux pane inherits the tmux SERVER environment (not this script's # environment). A dir on the launcher's PATH may be absent from the server PATH, # so every existing candidate must always be included. Dedup within the # constructed prefix avoids listing the same dir twice. _build_runtime_bin_prefix() { local candidates=() if [ -n "${MOSAIC_RUNTIME_BIN:-}" ]; then candidates+=("$MOSAIC_RUNTIME_BIN") fi if command -v npm >/dev/null 2>&1; then local npm_prefix npm_prefix=$(npm config get prefix 2>/dev/null) || true if [ -n "$npm_prefix" ]; then candidates+=("${npm_prefix}/bin") fi fi candidates+=("$HOME/.npm-global/bin") candidates+=("$HOME/.local/bin") local prefix="" for dir in "${candidates[@]}"; do [ -d "$dir" ] || continue if [ -z "$prefix" ]; then prefix="$dir" else case ":${prefix}:" in *":${dir}:"*) ;; # already in our prefix — skip *) prefix="${prefix}:${dir}" ;; esac fi done printf '%s' "$prefix" } MOSAIC_RUNTIME_BIN_PREFIX=$(_build_runtime_bin_prefix) # ── Build the pane command ──────────────────────────────────────────────────── # The pane command must: # - Export the augmented PATH so the runtime binary is found. # - exec the agent command so the runtime is the pane's foreground process # (makes `fleet ps` pane_current_command check reliable; no DRIFT false-positive). # # Quoting strategy: single-quote the inner shell snippet so that variable # references in MOSAIC_AGENT_COMMAND are NOT expanded here — they expand inside # the pane shell. However, MOSAIC_RUNTIME_BIN_PREFIX and PATH must be expanded # NOW (in this script) because the pane shell inherits the tmux server # environment, not this script's env. # # We build the snippet as a double-quoted here-string embedded in a printf call # to avoid nested quoting problems. if [ -n "$MOSAIC_RUNTIME_BIN_PREFIX" ]; then PANE_SHELL_SNIPPET="export PATH=\"${MOSAIC_RUNTIME_BIN_PREFIX}:\${PATH}\"; exec ${MOSAIC_AGENT_COMMAND}" else PANE_SHELL_SNIPPET="exec ${MOSAIC_AGENT_COMMAND}" fi mkdir -p "$MOSAIC_AGENT_WORKDIR" exec tmux -L "$MOSAIC_TMUX_SOCKET" new-session -d -s "$AGENT_NAME" -c "$MOSAIC_AGENT_WORKDIR" \ bash -c "$PANE_SHELL_SNIPPET"