#!/usr/bin/env bash
# lib.sh — shared library for the multi-agent-mux-* skills.
#
# Single source of truth for the four things that were inconsistently
# re-implemented across create/resume/delete/monitor (REVIEW.md §4.1):
#   - derive_session_name : the herdr session slug                               (P0-A)
#   - atomic_dump_yaml     : SQLite db transaction + temp+rename + .bak + validate (P0-B)
#   - env_python           : env-safe Python (no heredoc injection)             (P0-B / P1-B)
#   - find_workspace_uuid  : workspace-SCOPED resume id lookup                 (P0-C)
#
# Source it from each script with a path computed from the script location:
#   source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
#
# HARD RULE: the agent-sessions.yaml file is only ever written through
# atomic_dump_yaml. Never `open(yaml_path, 'w')` anywhere else.

if [ -z "${BASH_VERSION:-}" ]; then
  echo "ERROR: lib.sh must be executed/sourced from bash (foreign shell detected)" >&2
  # Reachable when this file is executed directly rather than sourced;
  # `return` only fails (falling through to `exit`) in that path.
  # shellcheck disable=SC2317
  return 1 2>/dev/null || exit 1
fi
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}"
WORKSPACE_ROOT="${WORKSPACE_ROOT:-$(cd "$SKILL_DIR/../.." && pwd)}"
export WORKSPACE_ROOT

# Framework semantic version. Single runtime source of truth; kept in lockstep
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
MAM_VERSION="4.1.2"
export MAM_VERSION
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"

_sanitize_herdr_agent_name() {
  local n="$1"
  if [ -z "$n" ]; then
    printf 'agent\n'
    return 0
  fi
  local s
  s=$(echo "$n" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g')
  if [[ ! "$s" =~ ^[a-z] ]]; then s="x-$s"; fi
  if [ "${#s}" -gt 32 ]; then
    local h=""
    if command -v shasum >/dev/null 2>&1; then
      h=$(printf '%s' "$s" | shasum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
    elif command -v sha1sum >/dev/null 2>&1; then
      h=$(printf '%s' "$s" | sha1sum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
    elif command -v openssl >/dev/null 2>&1; then
      h=$(printf '%s' "$s" | openssl sha1 2>/dev/null | awk '{print $NF}' | cut -c 1-8)
    fi
    if [ -n "$h" ]; then
      s="${s:0:23}-$h"
    else
      s="${s:0:32}"
    fi
  fi
  printf '%s\n' "$s"
}

# Add common Homebrew and local binary paths to PATH to ensure they are available in non-interactive shells
for dir in /home/linuxbrew/.linuxbrew/bin /home/linuxbrew/.linuxbrew/sbin "$HOME/.local/bin" "$HOME/.npm-global/bin"; do
  if [ -d "$dir" ] && [[ ":$PATH:" != *":$dir:"* ]]; then
    export PATH="$PATH:$dir"
  fi
done

# Central TUI dialog and readiness validation tokens (OP-6)
_MAM_MODAL_TOKENS='Do you trust the files|Yes, proceed|No, exit|Allow this|browser to authenticate|Resuming the full session|Resume from summary|Try the new fullscreen renderer\?'
_MAM_HINT_TOKENS='Use arrow keys|Esc to cancel|Press Enter to continue'
_MAM_DIALOG_TOKENS="${_MAM_MODAL_TOKENS}|${_MAM_HINT_TOKENS}"
_MAM_READY_TOKENS_CLAUDE='Anthropic|Assistant|Chat|Welcome|Claude Code|Opus|Sonnet|Haiku'

# Workspace-relative defaults with environment overrides (Phase Z)
HOME_DIR="${HOME_DIR:-$HOME}"
CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$HOME/.claude/projects}"
if [ -z "${LOCAL_BIN:-}" ]; then
  if [ -x "$HOME_DIR/bin/herdr" ]; then
    LOCAL_BIN="$HOME_DIR/bin"
  elif [ -x "$HOME_DIR/.local/bin/herdr" ]; then
    LOCAL_BIN="$HOME_DIR/.local/bin"
  else
    LOCAL_BIN="$HOME/.local/bin"
  fi
fi
export HOME_DIR CLAUDE_PROJECT_DIR LOCAL_BIN

# ---------------------------------------------------------------------------
# Herdr Server Isolation support
# ---------------------------------------------------------------------------
# Paths to exclude when resolving the real herdr binary (shim/wrapper dirs).
_HERDR_SHIM_DIR_PATTERN="${_HERDR_SHIM_DIR_PATTERN:-/multi-agent-herdr-shim/}"
_HERDR_SKILLS_BIN_PATTERN="${_HERDR_SKILLS_BIN_PATTERN:-/.agents/skills/.bin}"

HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-default}"

_canonical_file() {
  local p="$1" t d b i=0
  while [ -L "$p" ] && [ "$i" -lt 40 ]; do
    t="$(readlink "$p" 2>/dev/null)" || break
    case "$t" in
      /*) p="$t" ;;
      *)  p="$(dirname "$p")/$t" ;;
    esac
    i=$((i + 1))
  done
  d="$(cd -P "$(dirname "$p")" 2>/dev/null && pwd -P)" || return 1
  b="$(basename "$p")"
  printf '%s/%s\n' "$d" "$b"
}

_is_shim_path() {
  case "/$1/" in
    *"/.mam/shim/"*|*-shim/*|*"$_HERDR_SHIM_DIR_PATTERN"*|*"$_HERDR_SKILLS_BIN_PATTERN"/*)
      return 0 ;;
  esac
  return 1
}

_resolve_real_herdr_path() {
  local dir cand save_ifs="$IFS" real_path=""
  IFS=:
  for dir in $PATH; do
    [ -n "$dir" ] || continue
    _is_shim_path "$dir" && continue
    [ -x "$dir/herdr" ] || continue
    cand="$(_canonical_file "$dir/herdr" 2>/dev/null)" || cand="$dir/herdr"
    [ -n "$cand" ] || cand="$dir/herdr"
    _is_shim_path "$cand" && continue
    real_path="$dir/herdr"
    break
  done
  IFS="$save_ifs"
  [ -n "$real_path" ] || return 1
  printf '%s\n' "$real_path"
}

has_real_herdr() {
  _resolve_real_herdr_path >/dev/null 2>&1
}

_init_herdr_isolation() {
  local wrapper_dir="$WORKSPACE_ROOT/.mam/shim"
  mkdir -p "$wrapper_dir"
  if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
    export PATH="$wrapper_dir:$PATH"
  fi

  local tmp_file="$wrapper_dir/herdr.tmp.$$.$RANDOM"
  cat <<'EOF' > "$tmp_file"
#!/usr/bin/env bash
# Herdr-to-Herdr translation shim wrapper
set -euo pipefail

# This shim can run as a standalone subprocess (e.g. spawned directly by
# herdr) without inheriting the parent shell's PYTHONPATH export from
# lib.sh, which breaks the `python3 -m lib_py...` / `from lib_py...` calls
# below. Re-derive it from the shim's own on-disk location instead of
# assuming inheritance.
_SHIM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_SKILL_DIR="$(cd "$_SHIM_DIR/../../.agents/skills" 2>/dev/null && pwd || true)"
[ -n "$_SKILL_DIR" ] && export PYTHONPATH="$_SKILL_DIR:${PYTHONPATH:-}"

# Dynamic real herdr path resolution to prevent infinite recursion
_resolve_real_herdr() {
  local dir save_ifs="$IFS" real_path=""
  IFS=:
  for dir in $PATH; do
    if [[ "$dir" != *".mam/shim"* ]] && [[ "$dir" != *"-shim"* ]] && [ -x "$dir/herdr" ]; then
      real_path="$dir/herdr"
      break
    fi
  done
  IFS="$save_ifs"
  if [ -z "$real_path" ]; then
    real_path="herdr"
  fi
  echo "$real_path"
}
REAL_HERDR=$(_resolve_real_herdr)

# Support parsing -L <session> before the subcommand
while [ "${1:-}" = "-L" ]; do
  if [ $# -lt 2 ]; then
    echo "herdr shim: -L requires an argument" >&2
    exit 1
  fi
  export HERDR_SESSION_NAME="$2"
  shift 2
done

_MAM_SESSION="${HERDR_SESSION_NAME:-default}"
if [ "$_MAM_SESSION" = "default" ]; then
  _MAM_SESSION=""
else
  _mam_session_running=$("$REAL_HERDR" session list --json 2>/dev/null | MAM_SESS="$_MAM_SESSION" python3 -c "
import sys, json, os
try:
    d = json.loads(sys.stdin.read())
    name = os.environ.get('MAM_SESS', '')
    print('1' if any(s.get('name') == name and s.get('running') for s in d.get('sessions', [])) else '0')
except Exception:
    print('0')
" 2>/dev/null || echo "0")
  if [ "$_mam_session_running" != "1" ]; then
    # Headless bootstrap avoids herdr's "nested herdr is disabled" guard that
    # blocks a normal interactive `herdr --session <name>` launch from inside
    # an existing herdr pane (which is how MAM's own agents usually run).
    # ISSUE-3: plain `nohup ... & disown` only ignores SIGHUP and drops bash's
    # own job-table tracking - it does NOT create a new process group/session,
    # so the daemon stays in the caller's PGID and dies when a process-group
    # signal (SIGTERM/SIGINT from a test runner, timeout wrapper, or parent
    # supervisor teardown) is broadcast to that group. setsid(1) is not
    # installed by default on macOS, so spawn via Python's
    # start_new_session=True (os.setsid in the child) instead - portable
    # across macOS and Linux, and lib_py already depends on Python.
    _mam_server_pid=$(python3 -c '
import subprocess, sys
p = subprocess.Popen(sys.argv[1:], start_new_session=True,
                      stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                      stdin=subprocess.DEVNULL)
print(p.pid)
' "$REAL_HERDR" --session "$_MAM_SESSION" server)
    for _mam_wait_i in $(seq 1 40); do
      [ -S "${HOME:-$HOME_DIR}/.config/herdr/sessions/$_MAM_SESSION/herdr.sock" ] && break
      kill -0 "$_mam_server_pid" 2>/dev/null || break
      sleep 0.25
    done
  fi
fi

_real_herdr() {
  local session="${HERDR_SESSION_NAME:-}"
  if [ "$session" = "default" ]; then
    session=""
  fi
  if [ -n "$session" ]; then
    "$REAL_HERDR" --session "$session" "$@"
  else
    "$REAL_HERDR" "$@"
  fi
}

_sanitize_herdr_agent_name() {
  local n="$1"
  if [ -z "$n" ]; then
    printf 'agent\n'
    return 0
  fi
  local s
  s=$(echo "$n" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g')
  if [[ ! "$s" =~ ^[a-z] ]]; then s="x-$s"; fi
  if [ "${#s}" -gt 32 ]; then
    local h=""
    if command -v shasum >/dev/null 2>&1; then
      h=$(printf '%s' "$s" | shasum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
    elif command -v sha1sum >/dev/null 2>&1; then
      h=$(printf '%s' "$s" | sha1sum 2>/dev/null | awk '{print $1}' | cut -c 1-8)
    elif command -v openssl >/dev/null 2>&1; then
      h=$(printf '%s' "$s" | openssl sha1 2>/dev/null | awk '{print $NF}' | cut -c 1-8)
    else
      h=$(python3 -c "import hashlib,sys; print(hashlib.sha1(sys.argv[1].encode()).hexdigest()[:8])" "$s" 2>/dev/null || echo "00000000")
    fi
    s="${s:0:23}-${h}"
  fi
  printf '%s\n' "${s:0:32}"
}

# ---------------------------------------------------------------------------
# Workspace scoping (ISSUE-3).
#
# Hard filter when HERDR_WORKSPACE_ID is set, else the workspace id this
# shim last persisted under $WORKSPACE_ROOT/.mam/herdr_workspace_id.
# Do not infer from cwd — that silently blocks legitimate cross-workspace
# lookups. No env and no file keeps the previous server-global lookup.
# ---------------------------------------------------------------------------
_herdr_ws_id_file() {
  if [ -n "${WORKSPACE_ROOT:-}" ]; then
    printf '%s\n' "$WORKSPACE_ROOT/.mam/herdr_workspace_id"
  fi
}

_herdr_persist_ws_id() {
  local id="$1" f
  [ -n "$id" ] || return 0
  f=$(_herdr_ws_id_file)
  [ -n "$f" ] || return 0
  mkdir -p "$(dirname "$f")" 2>/dev/null || true
  printf '%s\n' "$id" > "$f" 2>/dev/null || true
}

_herdr_ws_scope() {
  if [ -n "${HERDR_WORKSPACE_ID:-}" ]; then
    printf '%s\n' "$HERDR_WORKSPACE_ID"
    return 0
  fi
  local f id=""
  f=$(_herdr_ws_id_file)
  if [ -n "$f" ] && [ -f "$f" ]; then
    id=$(tr -d '[:space:]' < "$f" 2>/dev/null || true)
  fi
  printf '%s\n' "$id"
}

# _herdr_agent_get_scoped <name> [workspace_id]
#
# agent get with the same workspace predicate as _resolve_herdr_pane_id.
# Prints pane_id on stdout and returns 0 when the agent exists and is in
# scope. Returns 1 (prints nothing) if missing or in another workspace.
# Callers MUST wrap with `|| true`.
_herdr_agent_get_scoped() {
  local name="$1"
  local target_ws pid=""
  # Default scope is the explicit env var only. The persisted workspace
  # file is NOT used here: one MAM workspace can own several herdr
  # workspaces (layout overflow / parallel create), so a single file
  # must not reject a uniquely named agent. File scope applies to pane
  # list / label lookup via _herdr_ws_scope.
  if [ "$#" -ge 2 ]; then
    target_ws="$2"
  else
    target_ws="${HERDR_WORKSPACE_ID:-}"
  fi
  [ -n "$name" ] || return 1
  pid=$(_real_herdr agent get "$name" 2>/dev/null | TARGET_WS="$target_ws" python3 -c "
import sys, json, os
tws = os.environ.get('TARGET_WS', '')
try:
    a = json.load(sys.stdin).get('result', {}).get('agent', {})
    if tws and a.get('workspace_id') and a.get('workspace_id') != tws:
        sys.exit(1)
    pid = a.get('pane_id') or ''
    if pid:
        print(pid)
        sys.exit(0)
except Exception:
    pass
sys.exit(1)
" 2>/dev/null || echo "")
  if [ -n "$pid" ]; then
    printf '%s\n' "$pid"
    return 0
  fi
  return 1
}

# _resolve_herdr_pane_id <target> [workspace_id]
#
# Resolve a session name / label to a real pane_id ("wN:pM").
# Strict order (no substring matching at any step):
#   1. herdr agent get <sanitized_name> (workspace-scoped)
#   2. herdr agent get <raw_name> (workspace-scoped)
#   3. herdr pane list [--workspace WS] matching
#        3-a. label exact
#        3-b. name exact
#        3-c. agent exact
# On success print pane_id on stdout and return 0; on failure print nothing
# and return 1. Callers MUST wrap with `|| true` (shim runs under set -e).
_resolve_herdr_pane_id() {
  local target="$1"
  # Fast path: caller already passed a real pane_id (e.g. "w1E:p1") — skip
  # agent-get/pane-list resolution entirely.
  if [[ "$target" =~ ^w[A-Za-z0-9]+:p[A-Za-z0-9]+$ ]]; then
    printf '%s\n' "$target"
    return 0
  fi
  local target_ws="${2:-$(_herdr_ws_scope)}"
  # A caller may pass a workspace *label* (e.g. $MAM_WS_LABEL) rather than
  # the raw workspace_id — panes only carry workspace_id, so translate a
  # label to its id via `herdr workspace list` before filtering. Falls
  # back to the original value on any failure/no-match (permissive; keeps
  # prior behavior when $target_ws is already a raw id).
  if [ -n "$target_ws" ]; then
    local resolved_ws
    resolved_ws=$(_real_herdr workspace list 2>/dev/null | TARGET_WS="$target_ws" python3 -c "
import sys, json, os
tws = os.environ.get('TARGET_WS', '')
try:
    wss = json.load(sys.stdin).get('result', {}).get('workspaces', [])
    ids = {w.get('workspace_id') for w in wss}
    if tws in ids:
        print(tws)
        sys.exit(0)
    hit = next((w.get('workspace_id') for w in wss if w.get('label') == tws), '')
    if hit:
        print(hit)
        sys.exit(0)
except Exception:
    pass
sys.exit(1)
" 2>/dev/null || echo "$target_ws")
    [ -n "$resolved_ws" ] && target_ws="$resolved_ws"
  fi
  local sat pid=""
  sat=$(_sanitize_herdr_agent_name "$target")

  local cand get_ws
  if [ "$#" -ge 2 ]; then
    get_ws="$target_ws"
  else
    get_ws="${HERDR_WORKSPACE_ID:-}"
  fi
  for cand in "$sat" "$target"; do
    [ -n "$cand" ] || continue
    pid=$(_herdr_agent_get_scoped "$cand" "$get_ws" 2>/dev/null || true)
    [ -n "$pid" ] && break
  done

  if [ -z "$pid" ]; then
    local ws_flag=()
    [ -n "$target_ws" ] && ws_flag=(--workspace "$target_ws")
    pid=$(_real_herdr pane list "${ws_flag[@]+"${ws_flag[@]}"}" 2>/dev/null \
      | TARGET_NAME="$target" TARGET_SAN="$sat" TARGET_WS="$target_ws" WORKSPACE_ROOT="${WORKSPACE_ROOT:-$PWD}" python3 -c "
import sys, json, os
tn  = os.environ.get('TARGET_NAME', '')
tsa = os.environ.get('TARGET_SAN', '')
tws = os.environ.get('TARGET_WS', '')
ws_root = os.environ.get('WORKSPACE_ROOT', os.getcwd())
try:
    panes = json.load(sys.stdin).get('result', {}).get('panes', [])
    # Client-side filter in case an older herdr ignores --workspace.
    if tws:
        panes = [p for p in panes if p.get('workspace_id') == tws]
    # ISSUE-2: exact match first, then agent-sessions.yaml lookup fallback
    for key in ('label', 'name'):
        for p in panes:
            v = p.get(key)
            if v and (v == tn or v == tsa):
                pid = p.get('pane_id') or ''
                if pid:
                    print(pid)
                    sys.exit(0)
    agent_kind = None
    yaml_path = os.path.join(ws_root, '.mam', 'agent-sessions.yaml')
    if os.path.isfile(yaml_path):
        try:
            import yaml
            with open(yaml_path) as f:
                ydata = yaml.safe_load(f) or {}
            for s in ydata.get('herdr_sessions', []):
                if s.get('name') == tn or s.get('name') == tsa:
                    agent_kind = s.get('pane', {}).get('cmd') or ''
                    break
        except Exception as e:
            sys.stderr.write(f'warning: yaml load failed: {e}\n')
    # BUG-1: only trust the generic agent_kind fallback when the caller's
    # own target string IS the bare kind (e.g. target == 'agy'). Using a
    # *derived* kind (looked up via the yaml entry matched by tn/tsa) to
    # then broadly match any pane of this kind is unsound whenever another,
    # unrelated same-kind session is alive in the workspace - it misattributes
    # that unrelated pane as this target even though their names differ.
    if agent_kind and (tn == agent_kind or tsa == agent_kind):
        matching = [p.get('pane_id') for p in panes if p.get('agent') == agent_kind and p.get('pane_id')]
        if len(matching) == 1:
            print(matching[0])
            sys.exit(0)
        elif len(matching) > 1:
            # R-1: Ambiguous resolution with multiple same-kind panes — fail closed
            sys.exit(1)
except Exception:
    pass
sys.exit(1)
" 2>/dev/null || echo "")
  fi

  # Real pane_id values look like 'w1E:p1' — the workspace segment is
  # alphanumeric. A digits-only pattern would reject every live pane_id.
  if [[ "$pid" =~ ^w[A-Za-z0-9]+:p[A-Za-z0-9]+$ ]]; then
    printf '%s\n' "$pid"
    return 0
  fi
  return 1
}
cmd="${1:-}"
if [ -z "$cmd" ]; then
  echo "herdr shim: no command specified" >&2
  exit 1
fi
shift

case "$cmd" in
  agent)
    sub="${1:-}"
    shift || true
    _resolve_herdr_target() {
      local raw="$1"
      local sat
      sat=$(_sanitize_herdr_agent_name "$raw")
      if [ -n "$(_herdr_agent_get_scoped "$sat" 2>/dev/null || true)" ]; then
        echo "$sat"
        return 0
      fi
      if [ "$raw" != "$sat" ] && [ -n "$(_herdr_agent_get_scoped "$raw" 2>/dev/null || true)" ]; then
        echo "$raw"
        return 0
      fi
      local from_list
      from_list=$(_real_herdr agent list 2>/dev/null | TARGET_NAME="$raw" TARGET_SAN="$sat" TARGET_WS="$(_herdr_ws_scope)" python3 -c '
import sys, json, os
tn  = os.environ.get("TARGET_NAME", "")
tsa = os.environ.get("TARGET_SAN", "")
tws = os.environ.get("TARGET_WS", "")
try:
    d = json.loads(sys.stdin.read())
    agents = d.get("result", {}).get("agents", [])
    if tws:
        agents = [a for a in agents if a.get("workspace_id") == tws]
    for a in agents:
        name = a.get("name", "")
        if name and (name == tn or name == tsa):
            print(a.get("pane_id") or name)
            sys.exit(0)
except Exception:
    pass
sys.exit(1)
' 2>/dev/null || echo "")
      if [ -n "$from_list" ]; then
        echo "$from_list"
        return 0
      fi
      # Explicit-env miss: do not fall through to a name that agent prompt
      # would deliver into another workspace (ISSUE-3 / F-2b). File-only
      # scope does not trip this — see _herdr_agent_get_scoped.
      if [ -n "${HERDR_WORKSPACE_ID:-}" ]; then
        return 1
      fi
      echo "$raw"
    }

    if [ "$sub" = "prompt" ] && [ $# -ge 2 ]; then
      t="$1"
      txt="$2"
      shift 2
      tgt=$(_resolve_herdr_target "$t" || true)
      if [ -z "$tgt" ]; then
        echo "Error: no in-scope herdr agent for '$t'" >&2
        exit 1
      fi
      _real_herdr agent prompt "$tgt" "$txt" "$@"
    elif [ "$sub" = "get" ] && [ $# -ge 1 ]; then
      t="$1"
      shift
      tgt=$(_resolve_herdr_target "$t" || true)
      if [ -z "$tgt" ]; then
        echo "Error: no in-scope herdr agent for '$t'" >&2
        exit 1
      fi
      _real_herdr agent get "$tgt" "$@"
    elif [ "$sub" = "read" ] && [ $# -ge 1 ]; then
      t="$1"
      shift
      tgt=$(_resolve_herdr_target "$t" || true)
      if [ -z "$tgt" ]; then
        echo "Error: no in-scope herdr agent for '$t'" >&2
        exit 1
      fi
      _real_herdr agent read "$tgt" "$@"
    else
      _real_herdr agent "$sub" "$@"
    fi
    ;;
  has-session)
    sess=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -t)
          if [ $# -lt 2 ]; then
            echo "Error: -t requires a value" >&2
            exit 1
          fi
          sess="$2"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    if [ -n "$(_herdr_agent_get_scoped "$(_sanitize_herdr_agent_name "$sess")" 2>/dev/null || true)" ] \
       || [ -n "$(_herdr_agent_get_scoped "$sess" 2>/dev/null || true)" ]; then
      exit 0
    fi
    if _real_herdr agent list 2>/dev/null | TARGET_NAME="$sess" TARGET_WS="$(_herdr_ws_scope)" python3 -c '
import sys, json, os
try:
    sys.path.insert(0, os.path.abspath(".agents/skills"))
    from lib_py.agents.sanitize import sanitize_herdr_agent_name
except Exception:
    sanitize_herdr_agent_name = lambda s: s.lower()[:32] if s else "agent"
tn = os.environ.get("TARGET_NAME", "")
stn = sanitize_herdr_agent_name(tn)
tws = os.environ.get("TARGET_WS", "")
try:
    d = json.loads(sys.stdin.read())
    agents = d.get("result", {}).get("agents", [])
    if tws:
        agents = [a for a in agents if a.get("workspace_id") == tws]
    for a in agents:
        an = a.get("name", "")
        if an and (an == tn or an == stn):
            sys.exit(0)
except Exception:
    pass
sys.exit(1)
'; then
      exit 0
    fi
    if [ -n "$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)" ]; then
      exit 0
    fi
    exit 1
    ;;
  new-session)
    name="" ws="" run_cmd=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -s)
          if [ $# -lt 2 ]; then
            echo "Error: -s requires a value" >&2
            exit 1
          fi
          name="$2"
          shift 2
          ;;
        -c)
          if [ $# -lt 2 ]; then
            echo "Error: -c requires a value" >&2
            exit 1
          fi
          ws="$2"
          shift 2
          ;;
        -d) shift ;;
        -x|-y) shift 2 ;;
        *) run_cmd="$1"; shift ;;
      esac
    done
    if [ -z "$run_cmd" ]; then
      run_cmd="${SHELL:-/bin/bash}"
    fi

    parsed=$(python3 -c "
import sys, shlex
cmd = sys.argv[1]
tokens = shlex.split(cmd)
env_flags = []
binary_tokens = []
for tok in tokens:
    if '=' in tok and not binary_tokens:
        env_flags.append('--env ' + tok)
    else:
        binary_tokens.append(shlex.quote(tok))
print('\t'.join(env_flags) + '\n' + ' '.join(binary_tokens))
" "$run_cmd" 2>/dev/null || echo "")

    env_flags=""
    final_cmd="$run_cmd"
    if [ -n "$parsed" ]; then
      env_flags=$(echo "$parsed" | head -n 1 | tr '\t' ' ')
      final_cmd=$(echo "$parsed" | tail -n +2)
    fi

    kind=""
    case "$name" in
      *-creator-claude|*-planner-claude|*-reviewer-claude) kind="claude" ;;
      *-creator-agy|*-planner-agy|*-reviewer-agy)          kind="agy" ;;
      *-creator-hermes|*-planner-hermes|*-reviewer-hermes) kind="hermes" ;;
      *-creator-grok|*-planner-grok|*-reviewer-grok)      kind="grok" ;;
      *-creator-opencode|*-planner-opencode|*-reviewer-opencode) kind="opencode" ;;
      *)
        if echo "$name" | grep -qi "agy"; then kind="agy"
        elif echo "$name" | grep -qi "claude"; then kind="claude"
        elif echo "$name" | grep -qi "hermes"; then kind="hermes"
        elif echo "$name" | grep -qi "grok"; then kind="grok"
        elif echo "$name" | grep -qi "opencode"; then kind="opencode"
        elif echo "${final_cmd:-}" | grep -qi "agy"; then kind="agy"
        elif echo "${final_cmd:-}" | grep -qi "claude"; then kind="claude"
        elif echo "${final_cmd:-}" | grep -qi "hermes"; then kind="hermes"
        elif echo "${final_cmd:-}" | grep -qi "grok"; then kind="grok"
        elif echo "${final_cmd:-}" | grep -qi "opencode"; then kind="opencode"
        fi
        ;;
    esac

    # Strip duplicate agent binary name/path from final_cmd if present, preventing positional argument misinterpretation
    final_cmd=$(python3 -c "
import sys, shlex
cmd = sys.argv[1]
kind = sys.argv[2]
try:
    tokens = shlex.split(cmd)
    if tokens:
        first = tokens[0]
        if first in ('claude', 'agy', 'hermes', 'grok', 'opencode') or any(first.endswith('/' + a) for a in ('claude', 'agy', 'hermes', 'grok', 'opencode')) or (kind and (first == kind or first.endswith('/' + kind))):
            tokens = tokens[1:]
    print(' '.join(shlex.quote(t) for t in tokens))
except Exception:
    print(cmd)
" "$final_cmd" "$kind" 2>/dev/null || echo "$final_cmd")

    # W1: Resolve existing_ws by querying pane list for matching CWD (since WorkspaceInfo has no cwd key)
    existing_ws=$(_real_herdr pane list 2>/dev/null | TARGET_CWD="${ws:-.}" python3 -c "
import sys, json, os
target_ws = os.environ.get('TARGET_CWD', '')
try:
    target_abs = os.path.realpath(target_ws) if target_ws else ''
    d = json.loads(sys.stdin.read())
    panes = d.get('result', {}).get('panes', [])
    matched_ws = ''
    for p in panes:
        p_cwd = os.path.realpath(p.get('cwd', '')) if p.get('cwd') else ''
        if target_abs and p_cwd == target_abs and p.get('workspace_id'):
            matched_ws = p.get('workspace_id')
            break
    print(matched_ws)
except Exception:
    pass
" 2>/dev/null || echo "")

    ws_id=""
    target_pane=""
    if [ -n "$existing_ws" ]; then
      # BUG-4: prefer an already-idle (agent-less) pane in the existing
      # workspace over splitting a new one — avoids unbounded pane
      # proliferation and 2x2 grid-layout churn when a prior agent's pane
      # was left behind idle.
      idle_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
import sys, json, os
target_ws = os.environ.get('TARGET_WS', '')
try:
    d = json.loads(sys.stdin.read())
    panes = d.get('result', {}).get('panes', [])
    for p in panes:
        if p.get('workspace_id') == target_ws and not p.get('agent') and p.get('pane_id'):
            print(p.get('pane_id'))
            break
except Exception:
    pass
" 2>/dev/null || echo "")

      if [ -n "$idle_pane" ]; then
        target_pane="$idle_pane"
        if [ -n "$final_cmd" ]; then
          idle_env="${env_flags//--env /}"
          idle_env="${idle_env:+$idle_env }HERDR_WORKSPACE_ID=$existing_ws"
          final_cmd="env $idle_env $final_cmd"
        fi
      else
      # W2a: Determine split direction policy via pane layout
      sample_pane=$(_real_herdr pane list 2>/dev/null | TARGET_WS="$existing_ws" python3 -c "
import sys, json, os
target_ws = os.environ.get('TARGET_WS', '')
try:
    d = json.loads(sys.stdin.read())
    panes = d.get('result', {}).get('panes', [])
    for p in panes:
        if p.get('workspace_id') == target_ws and p.get('pane_id'):
            print(p.get('pane_id'))
            break
except Exception:
    pass
" 2>/dev/null || echo "")

      split_dir=""
      if [ -n "$sample_pane" ]; then
        layout_raw=$(_real_herdr pane layout --pane "$sample_pane" 2>/dev/null || echo "")
        read -r split_dir split_target < <(printf '%s' "$layout_raw" | python3 -m lib_py.layout --min-cols "${MAM_MIN_PANE_COLS:-15}" --min-rows "${MAM_MIN_PANE_ROWS:-0}" --max-cols "${MAM_MAX_PANE_COLS:-2}" --max-rows "${MAM_MAX_PANE_ROWS:-2}" --sample-pane "$sample_pane" 2>/dev/null || echo "right $sample_pane")
        split_dir="${split_dir:-right}"
        sample_pane="${split_target:-$sample_pane}"
      fi

      if [ "$split_dir" = "right" ] || [ "$split_dir" = "down" ]; then
        split_json=$(_real_herdr pane split --pane "$sample_pane" --direction "$split_dir" --cwd "${ws:-.}" $env_flags --env HERDR_WORKSPACE_ID="$existing_ws" --no-focus 2>/dev/null || echo "")
        target_pane=$(echo "$split_json" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    res = d.get('result', {})
    print(res.get('pane', {}).get('pane_id') or res.get('pane_id', ''))
except Exception:
    pass
" 2>/dev/null || echo "")
      elif [ "$split_dir" = "overflow" ]; then
        # W2b: Overflow threshold reached — force create fresh workspace
        existing_ws=""
      else
        split_json=$(_real_herdr pane split --pane "$sample_pane" --direction right --cwd "${ws:-.}" $env_flags --env HERDR_WORKSPACE_ID="$existing_ws" --no-focus 2>/dev/null || echo "")
        target_pane=$(echo "$split_json" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    res = d.get('result', {})
    print(res.get('pane', {}).get('pane_id') or res.get('pane_id', ''))
except Exception:
    pass
" 2>/dev/null || echo "")
      fi
      fi
    fi

    if [ -z "$existing_ws" ] || [ -z "$target_pane" ]; then
      ws_json=$(_real_herdr workspace create --cwd "${ws:-.}" ${MAM_WS_LABEL:+--label "$MAM_WS_LABEL"} $env_flags --no-focus 2>/dev/null || echo "")
      target_pane=$(echo "$ws_json" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    res = d.get('result', {})
    w_obj = res.get('workspace', {})
    print(res.get('root_pane', {}).get('pane_id') or w_obj.get('root_pane_id') or res.get('root_pane_id') or res.get('pane_id', ''))
except Exception:
    pass
" 2>/dev/null || echo "")
      ws_id=$(echo "$ws_json" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    res = d.get('result', {})
    print(res.get('workspace', {}).get('workspace_id') or '')
except Exception:
    pass
" 2>/dev/null || echo "")
    else
      if [ -n "${MAM_WS_LABEL:-}" ]; then
        _real_herdr workspace rename "$existing_ws" "$MAM_WS_LABEL" >/dev/null 2>&1 || true
      fi
      ws_id="$existing_ws"
    fi
    if [ -n "${ws_id:-}" ]; then
      export HERDR_WORKSPACE_ID="$ws_id"
      _herdr_persist_ws_id "$ws_id"
    fi

    if [ -z "$target_pane" ]; then
      echo "Error: target_pane allocation failed for $name" >&2
      exit 1
    fi
    if [ -z "$kind" ]; then
      echo "Error: unable to determine agent kind for session $name" >&2
      exit 1
    fi

    # W5/W6: Backoff retries (0.5 -> 1 -> 2), abort immediately on usage or unknown flag errors
    res=""
    success=0
    backoffs=(0.5 1 2)
    agent_name=$(_sanitize_herdr_agent_name "$name")
    for i in $(seq 0 2); do
      if [ -n "$final_cmd" ]; then
        res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\" -- $final_cmd" 2>&1 || true)
      else
        res=$(eval "_real_herdr agent start \"$agent_name\" --kind \"$kind\" --pane \"$target_pane\"" 2>&1 || true)
      fi
      if echo "$res" | grep -qiE "^usage:|unknown option|unknown flag|missing required|invalid_agent_name|^error:"; then
        break
      fi
      # Fatal CLI errors abort immediately. agent_not_ready is the
      # documented "process is up, blocked on a dialog" status — continue
      # to wait_for_tui_ready. Do NOT treat "timed out waiting for agent
      # startup" as success: herdr returns that for a dead process too.
      if echo "$res" | grep -qE "agent_started|agent_not_ready"; then
        success=1
        break
      fi
      if [ "$i" -lt 2 ]; then
        sleep "${backoffs[$i]}"
      fi
    done

    if [ "$success" -ne 1 ]; then
      # A "timed out waiting for agent startup" result is ambiguous — herdr
      # returns the identical text whether the process died or the TUI
      # render was just slow (see the comment above the retry loop). Rather
      # than trust that text either way, re-query herdr directly: if the
      # agent is actually registered and alive, treat this as success; if
      # not, fall back to sending the launch command straight into the
      # pane before giving up.
      if echo "$res" | grep -qiE "timed out waiting for agent startup"; then
        if [ -n "$(_herdr_agent_get_scoped "$agent_name" 2>/dev/null || true)" ]; then
          success=1
        else
          if [ -n "$final_cmd" ]; then
            _real_herdr pane send-text "$target_pane" "$final_cmd" >/dev/null 2>&1 || true
            _real_herdr pane send-keys "$target_pane" Enter >/dev/null 2>&1 || true
          fi
          sleep 1
          if [ -n "$(_herdr_agent_get_scoped "$agent_name" 2>/dev/null || true)" ]; then
            success=1
          fi
        fi
      fi
    fi

    if [ "$success" -ne 1 ]; then
      echo "$res" >&2
      exit 1
    fi
    ;;
  kill-session)
    sess=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -t)
          if [ $# -lt 2 ]; then
            echo "Error: -t requires a value" >&2
            exit 1
          fi
          sess="$2"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    # NOTE: herdr's `session` verb operates on whole server instances (see
    # `herdr session list`), not individual agent panes — `session stop/delete
    # "$sess"` with a MAM session name always fails (silently, via `|| true`).
    # Resolve the real pane_id via `agent get` and close just that pane instead.
    agent_target=$(_sanitize_herdr_agent_name "$sess")
    pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
    if [ -n "$pane_id" ]; then
      _real_herdr pane close "$pane_id" >/dev/null 2>&1 || true
    fi
    _real_herdr kill-session -t "$agent_target" >/dev/null 2>&1 \
      || _real_herdr kill-session -t "$sess" >/dev/null 2>&1 || true
    ;;
  list-panes)
    sess="" format=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -t)
          if [ $# -lt 2 ]; then
            echo "Error: -t requires a value" >&2
            exit 1
          fi
          sess="$2"
          shift 2
          ;;
        -F)
          if [ $# -lt 2 ]; then
            echo "Error: -F requires a value" >&2
            exit 1
          fi
          format="$2"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    # NOTE: `herdr agent get <target>` returns {"result": {"agent": {...}}} —
    # there is no top-level "pane" key, and no "pid" field at all (only
    # "pane_id", herdr's own wN:pN identifier). The real OS pid requires a
    # second call to `pane process-info --pane <pane_id>`.
    agent_target=$(_sanitize_herdr_agent_name "$sess")
    info=$(_real_herdr agent get "$agent_target" 2>/dev/null || _real_herdr agent get "$sess" 2>/dev/null || true)
    pane_id="" cwd="" cmd=""
    if [ -n "$info" ]; then
      parsed=$(python3 -c "
import sys, json
try:
    raw = sys.stdin.read()
    if '{' in raw:
        raw = raw[raw.index('{'):]
    a = json.loads(raw).get('result', {}).get('agent', {})
except Exception:
    a = {}
print(a.get('pane_id', ''))
print(a.get('cwd', ''))
print(a.get('agent', ''))
" <<< "$info")
      pane_id=$(sed -n '1p' <<< "$parsed")
      cwd=$(sed -n '2p' <<< "$parsed")
      cmd=$(sed -n '3p' <<< "$parsed")
    fi
    pid=""
    if [ -n "$pane_id" ]; then
      case "$format" in
        *pane_pid*)
          pid=$(_real_herdr pane process-info --pane "$pane_id" 2>/dev/null | python3 -c "
import sys, json
try:
    pi = json.loads(sys.stdin.read()).get('result', {}).get('process_info', {})
    fg = pi.get('foreground_processes') or []
    print(fg[0]['pid'] if fg else pi.get('shell_pid', ''))
except Exception:
    pass
")
          ;;
      esac
    fi
    case "$format" in
      *'#{pane_pid}'*'#{pane_current_path}'*|*#{pane_pid}*#{pane_current_path}*)
        printf '%s|%s|%s\n' "$pid" "$cwd" "$cmd"
        ;;
      *'#{pane_pid}'*|*#{pane_pid}*)
        printf '%s\n' "$pid"
        ;;
      *'#{pane_current_path}'*|*#{pane_current_path}*)
        printf '%s\n' "$cwd"
        ;;
      *'#{pane_current_command}'*|*#{pane_current_command}*)
        printf '%s\n' "$cmd"
        ;;
      *)
        printf '%s|%s|%s\n' "$pid" "$cwd" "$cmd"
        ;;
    esac
    ;;
  capture-pane)
    sess=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -t)
          if [ $# -lt 2 ]; then
            echo "Error: -t requires a value" >&2
            exit 1
          fi
          sess="$2"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    agent_target=$(_sanitize_herdr_agent_name "$sess")
    pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
    if [ -n "$pane_id" ]; then
      _real_herdr pane read "$pane_id" --source visible --lines 100 2>/dev/null \
        || _real_herdr agent read "$agent_target" --source visible --lines 100 2>/dev/null \
        || _real_herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true
    else
      _real_herdr agent read "$agent_target" --source visible --lines 100 2>/dev/null \
        || _real_herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true
    fi
    ;;
  send-keys)
    sess="" key=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -t)
          if [ $# -lt 2 ]; then
            echo "Error: -t requires a value" >&2
            exit 1
          fi
          sess="$2"
          shift 2
          ;;
        *) key="$1"; shift ;;
      esac
    done
    if [ "$key" = "C-m" ] || [ "$key" = "Enter" ]; then
      key="Enter"
    fi
    # `pane send-keys` requires a real pane_id ("wN:pN"), not an agent name —
    # resolve it via `agent get` first (agent-level commands accept names).
    agent_target=$(_sanitize_herdr_agent_name "$sess")
    pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
    if [ -n "$pane_id" ]; then
      _real_herdr pane send-keys "$pane_id" "$key" >/dev/null 2>&1 || true
    else
      _real_herdr pane send-keys "$agent_target" "$key" >/dev/null 2>&1 || _real_herdr pane send-keys "$sess" "$key" >/dev/null 2>&1 || true
    fi
    ;;
  set-buffer)
    buf="tmp_buffer"
    text=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -b)
          if [ $# -lt 2 ]; then
            echo "Error: -b requires a value" >&2
            exit 1
          fi
          buf="$(echo "$2" | tr -cd 'A-Za-z0-9_.-')"
          if [ -z "$buf" ]; then
            echo "Error: -b name contains no usable characters" >&2
            exit 1
          fi
          buf="buf_$buf"
          shift 2
          ;;
        *) text="$1"; shift ;;
      esac
    done
    # A-3 GC: unique buffer names mean an abandoned buffer (SIGINT/SIGTERM/
    # timeout between set-buffer and delete-buffer) is never overwritten, so
    # it would leak forever. Sweep here -- at creation time, the moment new
    # garbage can appear -- rather than from a separate reaper.
    #
    # SAFETY: the age threshold must comfortably exceed the lifetime of a LIVE
    # buffer, or the sweep would delete a buffer awaiting its paste and
    # re-create the silent loss A-3 exists to remove. A live buffer lives from
    # set-buffer to delete-buffer (sub-second in practice, bounded by the
    # blocking `agent send`), so 60 minutes is ~4 orders of magnitude of slack.
    # Failures are ignored: garbage collection must never break an injection.
    #
    # The '.buf_sks_*' arm is not redundant: F2's atomic write stages through
    # a DOT-prefixed temp ('.buf_sks_....tmp'), which 'buf_sks_*' cannot match.
    # Without it a temp orphaned by SIGKILL between write and rename would leak
    # forever, exactly like the buffers this sweep exists to reclaim.
    buffer_dir="${WORKSPACE_ROOT:+$WORKSPACE_ROOT/.mam/buffers}"
    buffer_dir="${buffer_dir:-${TMPDIR:-/tmp}/mam_buffers}"
    mkdir -p "$buffer_dir" 2>/dev/null || true
    find "$buffer_dir" \( -name 'buf_sks_*' -o -name '.buf_sks_*' \) \
      -mmin +${MAM_BUFFER_GC_MINUTES:-60} -delete 2>/dev/null || true
    _tmp="$buffer_dir/.$buf.$$.tmp"
    if ! { echo -n "$text" > "$_tmp" && mv -f "$_tmp" "$buffer_dir/$buf"; }; then
      rm -f "$_tmp"
      echo "Error: failed to write buffer $buf" >&2
      exit 1
    fi
    ;;
  paste-buffer)
    buf="tmp_buffer"
    sess=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -b)
          buf="$(echo "$2" | tr -cd 'A-Za-z0-9_.-')"
          if [ -z "$buf" ]; then
            echo "Error: -b name contains no usable characters" >&2
            exit 1
          fi
          buf="buf_$buf"
          shift 2
          ;;
        -t)
          if [ $# -lt 2 ]; then
            echo "Error: -t requires a value" >&2
            exit 1
          fi
          sess="$2"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    buffer_dir="${WORKSPACE_ROOT:+$WORKSPACE_ROOT/.mam/buffers}"
    buffer_dir="${buffer_dir:-${TMPDIR:-/tmp}/mam_buffers}"
    if [ ! -f "$buffer_dir/$buf" ]; then
      echo "Error: buffer $buf not found ($buffer_dir/$buf)" >&2
      exit 1
    fi
    pane_id=$(_resolve_herdr_pane_id "$sess" 2>/dev/null || true)
    if [ -z "$pane_id" ]; then
      # herdr has no `agent send` subcommand. Returning success here would let
      # send_keys_safe submit an empty prompt.
      echo "Error: paste-buffer could not resolve a pane for '$sess'" >&2
      exit 1
    fi
    # Insert only. Submission is owned exclusively by send_keys_safe (ISSUE-1).
    # A combined insert-and-submit command would double-submit.
    if ! _real_herdr pane send-text "$pane_id" "$(cat "$buffer_dir/$buf")" >/dev/null 2>&1; then
      echo "Error: pane send-text failed for '$sess' ($pane_id)" >&2
      exit 1
    fi
    ;;
  delete-buffer)
    buf="tmp_buffer"
    while [ $# -gt 0 ]; do
      case "$1" in
        -b)
          buf="$(echo "$2" | tr -cd 'A-Za-z0-9_.-')"
          if [ -z "$buf" ]; then
            echo "Error: -b name contains no usable characters" >&2
            exit 1
          fi
          buf="buf_$buf"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    buffer_dir="${WORKSPACE_ROOT:+$WORKSPACE_ROOT/.mam/buffers}"
    buffer_dir="${buffer_dir:-${TMPDIR:-/tmp}/mam_buffers}"
    rm -f "$buffer_dir/$buf"
    ;;
  ls)
    format=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -F)
          if [ $# -lt 2 ]; then
            echo "Error: -F requires a value" >&2
            exit 1
          fi
          format="$2"
          shift 2
          ;;
        *) shift ;;
      esac
    done
    # herdr has no session-creation timestamp: `agent list`, `agent get`,
    # `pane get` and `api snapshot` all lack one. Derive it from the OS as the
    # start time of the pane's ROOT process (`shell_pid`).
    #
    # It must NOT come from `foreground_processes[0]`: under MAM that slot
    # holds the `caffeinate -i -t 300` keep-awake wrapper, which respawns every
    # five minutes. Its start time creeps forward, so a session idle for longer
    # than one caffeinate cycle would look "created after" its own transcript
    # and be discarded by the stale-transcript guard in verify_session_uuid.
    #
    # This runs as ONE python process that re-implements `_real_herdr` and
    # batches a single `ps` over every pid, rather than forking python and ps
    # once per agent inside a shell loop: reconcile calls this on a 15s
    # heartbeat, so per-agent forking shows up as a standing cost.
    #
    # TZ=UTC pins both `ps` (which prints lstart in the caller's zone) and
    # `mktime` (which reads it back in the caller's zone) to the same zone.
    # They already agree at any single TZ, so this is not about skew between
    # them -- it removes the once-a-year DST ambiguity in local-time mktime.
    # LC_ALL=C pins lstart's field names, which are locale-dependent.
    _sess="${HERDR_SESSION_NAME:-}"
    if [ "$_sess" = "default" ]; then
      _sess=""
    fi
    _real_herdr agent list 2>/dev/null | \
      MAM_LS_FORMAT="$format" MAM_REAL_HERDR="$REAL_HERDR" MAM_LS_SESSION="$_sess" \
      TZ=UTC LC_ALL=C python3 -c '
import json, os, subprocess, sys, time

fmt = os.environ.get("MAM_LS_FORMAT", "")
real = os.environ.get("MAM_REAL_HERDR") or "herdr"
sess = os.environ.get("MAM_LS_SESSION") or ""
base = [real] + (["--session", sess] if sess else [])

raw = sys.stdin.read()
try:
    agents = json.loads(raw).get("result", {}).get("agents", []) or []
except Exception:
    # Unparseable means "we do not know", not "there are no sessions".
    # Exiting nonzero lets reconcile fall into its herdr_confirmed=False path
    # instead of reading empty stdout as a confirmed zero and terminating
    # every live row it has on file.
    sys.exit(1)

rows = []
for a in agents:
    name = a.get("name") or a.get("agent") or "unknown"
    pane = a.get("pane_id") or ""
    pid = None
    if pane:
        try:
            out = subprocess.run(base + ["pane", "process-info", "--pane", pane],
                                 capture_output=True, text=True, timeout=10).stdout
            pi = json.loads(out).get("result", {}).get("process_info", {})
            p = pi.get("shell_pid") or pi.get("foreground_process_group_id")
            if isinstance(p, int) and p > 0:
                pid = p
        except Exception:
            pass
    rows.append((name, pid))

starts = {}
pids = sorted({p for _, p in rows if p})
if pids:
    try:
        out = subprocess.run(["ps", "-o", "pid=,lstart=", "-p", ",".join(str(p) for p in pids)],
                             capture_output=True, text=True, timeout=10).stdout
        for line in out.splitlines():
            line = line.strip()
            if not line:
                continue
            head, _, rest = line.partition(" ")
            try:
                starts[int(head)] = int(time.mktime(time.strptime(rest.strip(), "%a %b %d %H:%M:%S %Y")))
            except Exception:
                pass
    except Exception:
        pass

# Degrade to now, never to 0 or a sentinel. The consumer guard is
#   if epoch and mtime(transcript) < epoch: reject
# so an over-estimate only makes it stricter, while an under-estimate
# (0 is falsy; 999999 is 1970-01-12 and below every real mtime) switches
# the guard off outright -- which is the B-4 defect.
now = int(time.time())
for name, pid in rows:
    if fmt == "#{session_name}":
        print(name)
    else:
        print(name + "|" + str(starts.get(pid) or now))
'
    ;;
  *)
    _real_herdr "$cmd" "$@"
    ;;
esac
EOF
  chmod +x "$tmp_file" 2>/dev/null || true
  mv -f "$tmp_file" "$wrapper_dir/herdr" 2>/dev/null || rm -f "$tmp_file" 2>/dev/null || true
  if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
    export PATH="$wrapper_dir:$PATH"
  fi
}

mam_herdr() {
  _init_herdr_isolation
  "$WORKSPACE_ROOT/.mam/shim/herdr" "$@"
}

_herdr() {
  mam_herdr "$@"
}

herdr() {
  mam_herdr "$@"
}

# ---------------------------------------------------------------------------
# resolve_herdr_server <session_name>
#
# Query agent-sessions.yaml to find the herdr_session associated with a session.
# Fallback to HERDR_SESSION_NAME or HERDR_SERVER_NAME or workspace slug or 'default' if not registered.
# Prints the resolved server name on stdout.
# ---------------------------------------------------------------------------
# NOTE: Retained as small inline PYEOF block (39 lines) per Plan Rev.2 (Job 98393a97).
# Small blocks have minimal overhead and high local cohesion; extraction into lib_py is reserved for large blocks (>200 lines).
load_state_json() {
  env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
import os, sys, sqlite3, json, yaml
yaml_path = os.environ['YAML_PATH']
db_path = os.path.splitext(yaml_path)[0] + '.db'
d = {}
db_sessions = []
try:
    if os.path.exists(db_path):
        conn = sqlite3.connect(db_path, timeout=60.0)
        conn.execute('PRAGMA busy_timeout = 60000')
        try:
            row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
            if row:
                d = json.loads(row[0])
        except sqlite3.OperationalError:
            pass
        try:
            cursor = conn.execute('SELECT data FROM sessions')
            for r in cursor.fetchall():
                db_sessions.append(json.loads(r[0]))
            d['herdr_sessions'] = db_sessions
        except sqlite3.OperationalError:
            pass
        conn.close()
    elif os.path.exists(yaml_path):
        with open(yaml_path) as f:
            d = yaml.safe_load(f) or {}
except Exception:
    pass
# Clean lone surrogates to prevent UnicodeEncodeError on stdout print
def clean_surrogates(obj):
    if isinstance(obj, str):
        return obj.encode('utf-8', errors='replace').decode('utf-8')
    elif isinstance(obj, dict):
        return {k: clean_surrogates(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [clean_surrogates(x) for x in obj]
    return obj
d = clean_surrogates(d)
print(json.dumps(d, ensure_ascii=False))
PYEOF
}

# resolve_agent_type_from_registry <session_name>
#
# 레지스트리(YAML/DB)에 기록된 사실로 에이전트 종류를 해석한다. 우선순위는
# lib_py.agents.registry.agent_of_row 의 계약을 그대로 따른다:
#   ① row['agent'] 명시 필드
#   ② 세션명 접미사 (*-{creator,planner,reviewer}-<agent> 및 *-<agent>)
#   ③ pane.cmd (정확히 일치하거나 .../<agent> 바이너리 경로)
# 성공하면 에이전트명을 stdout 에 출력하고 0 을, 셋 다 실패하면 아무것도
# 출력하지 않고 1 을 반환한다. 오류 메시지는 호출자가 소유한다 — 각 스크립트가
# 문서화한 종료 코드를 그대로 유지하기 위해서다.
#
# NOTE: agent_of_row 의 match_cmd=True 는 "비-입양 조회" 계약이다. reconcile.sh
#       입양 루프는 이 헬퍼를 쓰면 안 된다 (3aee63cf §1.2 실측 반증).
resolve_agent_type_from_registry() {
  local name="$1"
  MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$name" python3 -c "
import os, json, sys
from lib_py.agents.registry import agent_of_row
name = os.environ['SESSION_NAME']
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
row = next((s for s in d.get('herdr_sessions', []) if s.get('name') == name), {})
resolved = agent_of_row(row, session_name=name)
if not resolved:
    sys.exit(1)
print(resolved)
"
}

# resolve_herdr_session <session_name> [workspace]
#
# returns the isolated herdr *session* name (socket/daemon) to use for this MAM session row,
# not a workspace label. Real isolation is `--session <name>` (see `_MAM_SESSION` in the
# generated wrapper) — a workspace label match provides no actual isolation
# since agent/pane commands are server-global regardless of workspace.

resolve_herdr_session() {
  local session_name="$1"
  local workspace="${2:-}"
  MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" TARGET_WS="$workspace" python3 -c "
import sys, os, json
name = os.environ['SESSION_NAME']
ws = os.environ.get('TARGET_WS', '').strip()
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
for s in d.get('herdr_sessions', []):
    if s.get('name') == name:
        # herdr_workspace 는 워크스페이스 *라벨* 이지 소켓 이름이 아니다.
        val = s.get('herdr_session') or s.get('herdr_server')
        if val and val != 'default':
            print(val)
            sys.exit(0)
fallback = ''
if ws:
    abs_ws = os.path.abspath(ws)
    parent = os.path.basename(os.path.dirname(abs_ws)) or 'workspace'
    work = os.path.basename(abs_ws) or 'root'
    if parent in ('/', '.'): parent = 'workspace'
    if work in ('/', '.'): work = 'root'
    slug = f'{parent}-{work}'.lower().replace('_', '-')
    import re
    slug = re.sub(r'[^a-zA-Z0-9-]', '', slug).lstrip('-')
    fallback = f'mam-{slug}' if slug else 'mam-ws'
else:
    fallback = os.environ.get('HERDR_SESSION_NAME', '') or os.environ.get('HERDR_SERVER_NAME', '')
    if not fallback or fallback == 'default':
        fallback = 'default'
        print('WARN: resolve_herdr_session called without workspace parameter for unregistered session; falling back to default', file=sys.stderr)
print(fallback or 'default')
"
}

# resolve_herdr_workspace <session_name> [workspace]
#
# 이 MAM 세션 행의 워크스페이스 *라벨* 을 돌려준다. herdr 소켓/데몬 이름이
# 아니다 — 그쪽은 resolve_herdr_session() 이다. 라벨이 소켓 인자로 흘러가면
# reconcile.sh 가 엉뚱한 소켓에 kill-session 을 날린다.
#
# 우선순위 (C-1: 등록된 행의 사실이 호출자 인자를 이긴다):
#   ① row['herdr_workspace']            — 명시 기록
#   ② row['pane']['cwd'] 의 슬러그       — 등록된 세션의 실제 작업 디렉터리
#   ③ 인자 workspace 의 슬러그           — 미등록 세션 전용 폴백
#   ④ 빈 문자열
# 주의 1: herdr_session / herdr_server 로는 절대 폴백하지 않는다 (D4).
# 주의 2: create_session.sh 는 이 함수를 쓰지 않는다 — 재생성 시 낡은 행의
#         pane.cwd 를 물려받기 때문 (D5).
resolve_herdr_workspace() {
  local session_name="$1"
  local workspace="${2:-}"
  MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" TARGET_WS="$workspace" python3 -c "
import sys, os, json, re
name = os.environ['SESSION_NAME']
ws   = os.environ.get('TARGET_WS', '').strip()
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))

def slug(path):
    if not path:
        return ''
    a = os.path.abspath(path)
    parent = os.path.basename(os.path.dirname(a)) or 'workspace'
    work   = os.path.basename(a) or 'root'
    if parent in ('/', '.'): parent = 'workspace'
    if work   in ('/', '.'): work   = 'root'
    s = f'{parent}-{work}'.lower().replace('_', '-')
    return re.sub(r'[^a-zA-Z0-9-]', '', s).lstrip('-')

row = next((s for s in d.get('herdr_sessions', []) if s.get('name') == name), None)

# ① 명시 기록
if row and row.get('herdr_workspace'):
    print(row['herdr_workspace']); sys.exit(0)

# ② 등록된 행의 실제 cwd — 호출자 인자보다 우선 (C-1)
if row:
    derived = slug((row.get('pane') or {}).get('cwd', ''))
    if derived:
        print(derived); sys.exit(0)

# ③ 미등록(또는 cwd 부재) 세션 폴백
if ws:
    derived = slug(ws)
    if derived:
        print(derived); sys.exit(0)

print('')
"
}

# ---------------------------------------------------------------------------
# derive_workspace_slug <workspace>
# ---------------------------------------------------------------------------
derive_workspace_slug() {
  local workspace="${1:-$PWD}"
  local abs parent work slug
  abs="$(cd "$workspace" 2>/dev/null && pwd)" || abs="$workspace"
  parent="$(basename "$(dirname "$abs")" 2>/dev/null || echo "")"
  work="$(basename "$abs" 2>/dev/null || echo "root")"
  if [ -z "$parent" ] || [ "$parent" = "/" ] || [ "$parent" = "." ]; then
    parent="workspace"
  fi
  if [ -z "$work" ] || [ "$work" = "/" ] || [ "$work" = "." ]; then
    work="root"
  fi
  slug="$(printf '%s-%s' "$parent" "$work" | tr '[:upper:]' '[:lower:]' | tr '_' '-')"
  slug="$(printf '%s' "$slug" | tr -cd 'a-zA-Z0-9-')"
  slug="$(printf '%s' "$slug" | sed 's/^-*//')"
  if [ -z "$slug" ]; then
    slug="ws"
  fi
  printf 'mam-%s' "$slug"
}

# ---------------------------------------------------------------------------
# mam_gen_uuid
# ---------------------------------------------------------------------------
mam_gen_uuid() {
  if command -v uuidgen >/dev/null 2>&1; then
    uuidgen | tr 'A-Z' 'a-z'
  else
    python3 -c 'import uuid; print(uuid.uuid4())'
  fi
}

# ---------------------------------------------------------------------------
# mam_abs_workspace <path>
# ---------------------------------------------------------------------------
mam_abs_workspace() {
  local p="${1:-}"
  ( cd -P "$p" 2>/dev/null && pwd -P ) || printf '%s' "$p"
}

# ---------------------------------------------------------------------------
# mam_workspace_key <workspace>
# ---------------------------------------------------------------------------
mam_workspace_key() {
  printf '%s' "$(mam_abs_workspace "$1")" | tr '/_' '--'
}


# ---------------------------------------------------------------------------
# derive_session_name <workspace> <agent> [role]
# ---------------------------------------------------------------------------
derive_session_name() {
  local workspace="${1:-$PWD}" agent="${2:-}" role="${3:-creator}"
  role=$(echo "$role" | tr '[:upper:]' '[:lower:]')
  local base_slug
  base_slug="$(derive_workspace_slug "$workspace")"
  local slug="${base_slug#mam-}"
  if [ -n "$agent" ]; then
    printf '%s-%s-%s' "$slug" "$role" "$agent"
  else
    printf '%s-%s-' "$slug" "$role"
  fi
}

# ---------------------------------------------------------------------------
# env_python <yaml_path> [KEY=VALUE ...]   (Python source read from stdin)
#
# Run python3 with the source supplied on stdin via a *quoted* heredoc, so the
# shell never interpolates the source. All values are passed through the
# environment (YAML_PATH plus any KEY=VALUE pairs). Untrusted data (workspace
# paths, capture-pane text) must travel as env vars and be read via os.environ
# inside the script — never spliced into the source. Read-only by convention;
# use atomic_dump_yaml when you need to write the YAML.
# ---------------------------------------------------------------------------
_validate_env_key() {
  local key="$1"
  if [[ ! "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
    echo "ERROR: Invalid environment variable name: $key" >&2
    return 1
  fi
  case "$key" in
    LD_PRELOAD|LD_LIBRARY_PATH|PYTHONPATH|PYTHONHOME|PYTHONINSPECT|PYTHONSTARTUP)
      echo "ERROR: Blocked environment variable: $key" >&2
      return 1
      ;;
  esac
  return 0
}

env_python() {
  local yaml_path="$1"; shift
  local -a envs=("YAML_PATH=$yaml_path" "HOME_DIR=$HOME_DIR" "CLAUDE_PROJECT_DIR=$CLAUDE_PROJECT_DIR" "LOCAL_BIN=$LOCAL_BIN" "PYTHONPATH=$SKILL_DIR:${PYTHONPATH:-}")
  while [ $# -gt 0 ]; do
    case "$1" in
      *=*)
        local key="${1%%=*}"
        _validate_env_key "$key" || return 1
        envs+=("$1")
        shift
        ;;
      *)
        break
        ;;
    esac
  done
  local pybin
  pybin="$(_delegate_py_bin)"
  env "${envs[@]}" "$pybin" - "$@"
}

# ---------------------------------------------------------------------------
# atomic_dump_yaml <yaml_path> [KEY=VALUE ...]   (mutation source from stdin)
#
# The ONLY sanctioned way to write agent-sessions.yaml. It:
#   1. takes an exclusive SQLite BEGIN IMMEDIATE transaction lock on
#      agent-sessions.db (serialises all writers)
#   2. loads the current state into `d` (seeds from YAML if DB is empty)
#   3. exec()s the caller's mutation source (sees d, yaml, os, datetime,
#      timezone, glob, subprocess; reads values via os.environ). The mutation
#      may print and may `raise SystemExit(n)` to abort *without* writing.
#   4. validates the resulting schema
#   5. backs up to <yaml_path>.bak, then writes YAML atomically (temp + os.replace)
#      when a session transitions to a finished state.
#
# The mutation source is passed via env and exec()'d — it is never string
# spliced and untrusted data never lands in Python source (P0-B / P1-B).
# ---------------------------------------------------------------------------
# Check if the workspace is on NFS — locking behaves differently on NFS
_check_is_nfs() {
  local f="$1"
  local mountpoint
  mountpoint="$(df --output=target "$f" 2>/dev/null | tail -1)"
  if [ -z "$mountpoint" ]; then
    mountpoint="$(df -P "$f" 2>/dev/null | tail -1 | awk '{print $6}')"
  fi
  if [ -n "$mountpoint" ] && mount | grep -i -q -E "$mountpoint.*(nfs|cifs|smb|sshfs)"; then
    return 0 # is NFS
  fi
  return 1 # not NFS
}

atomic_dump_yaml() {
  local yaml_path="$1"; shift
  if [ -z "${MAM_IS_NFS:-}" ]; then
    if _check_is_nfs "$(dirname "$yaml_path")"; then
      export MAM_IS_NFS="true"
      echo "WARNING: $(dirname "$yaml_path") appears to be a network filesystem (NFS/CIFS/SSHFS)." >&2
      echo "WARNING: SQLite journal_mode automatically falls back to DELETE." >&2
    else
      export MAM_IS_NFS="false"
    fi
  fi

  local -a envs=("YAML_PATH=$yaml_path" "HOME_DIR=$HOME_DIR" "CLAUDE_PROJECT_DIR=$CLAUDE_PROJECT_DIR" "LOCAL_BIN=$LOCAL_BIN" "MAM_IS_NFS=$MAM_IS_NFS" "PYTHONPATH=$SKILL_DIR:${PYTHONPATH:-}")
  while [ $# -gt 0 ]; do
    case "$1" in
      *=*)
        local key="${1%%=*}"
        _validate_env_key "$key" || return 1
        envs+=("$1")
        shift
        ;;
      *)
        break
        ;;
    esac
  done
  local mutation=""; mutation="$(cat)"
  local pybin; pybin="$(_delegate_py_bin)"
  env "${envs[@]}" AGENT_SESSIONS_MUTATION="$mutation" "$pybin" - <<'PYEOF'
from lib_py.atomic_yaml import atomic_dump_yaml_main
atomic_dump_yaml_main()
PYEOF
}



# ---------------------------------------------------------------------------
# verify_session_uuid <workspace> <agent> <uuid> [session_row_json] [mode]
# Returns 0 if valid (passes Stage 1, 2, 3), 1 otherwise.
# ---------------------------------------------------------------------------
if [ -f "$SKILL_DIR/lib_py/verify_session.py" ]; then
  VERIFY_SESSION_PYTHON="$(cat "$SKILL_DIR/lib_py/verify_session.py")"
else
  echo "ERROR: lib_py/verify_session.py missing" >&2
  return 1 2>/dev/null || exit 1
fi

verify_session_uuid() {
  local workspace="$1" agent="$2" uuid="$3" row_json="${4:-}" mode="${5:-discover}"
  WS_ABS="$workspace" AGENT="$agent" UUID="$uuid" ROW_JSON="$row_json" MODE="$mode" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
import os, sys, json
from lib_py.verify_session import verify_session_uuid
ws = os.environ['WS_ABS']
agent = os.environ['AGENT']
uuid = os.environ['UUID']
row_str = os.environ.get('ROW_JSON', '')
row = json.loads(row_str) if row_str else {}
mode = os.environ.get('MODE', 'discover')
if verify_session_uuid(ws, agent, uuid, row, mode=mode):
    sys.exit(0)
sys.exit(1)
PYEOF
}

# verify_tui_viewport <session_name> <agent> <workspace>
#
# Viewport matching verification:
# | Return | Meaning | Action Required |
# |---|---|---|
# | 0 | Viewport matches workspace | Pin UUID, last_visible_status='pinned', drift class C |
# | 1 | Viewport mismatch | Do not pin, log C-warn, last_visible_status unchanged |
# | 2 | Viewport check unavailable | Pin UUID via stage 1-3 only, log C-degraded |
#
# Returns:
#   0 = verified match
#   1 = mismatch
#   2 = check not possible (capture failed, session gone)
verify_tui_viewport() {
  local session_name="$1" agent="$2" workspace="$3"
  local abs; abs="$(mam_abs_workspace "$workspace")"
  local base; base="$(basename "$abs")"
  
  if ! _herdr has-session -t "$session_name" >/dev/null 2>&1; then
    return 2
  fi
  
  local pane_content
  pane_content=$(_pane_capture "$session_name" 2>/dev/null || true)
  if [ -z "$pane_content" ]; then
    return 2
  fi
  
  local flat base_flat
  flat=$(printf '%s' "$pane_content" | tr -d '[:space:]')
  base_flat=$(printf '%s' "$base" | tr -d '[:space:]')
  if printf '%s' "$flat" | grep -Fq "$base_flat"; then
    return 0
  fi
  if printf '%s\n' "$pane_content" | grep -Eq '(^|[^[:alnum:]])/[A-Za-z0-9._-]+/[A-Za-z0-9._/-]+'; then
    return 1
  fi
  return 2
}

# ---------------------------------------------------------------------------
# find_workspace_uuid <workspace> <agent>
#
# Workspace-SCOPED resolution of the resume UUID (P0-C). Resolution order:
#   1) herdr_sessions[] row whose pane.cwd == this workspace -> per-row own id
#      (claude_session_id_own / agy_conversation_id_own)
#   2) on-disk scan scoped to this workspace, via the agent adapter's discover()
# Prints the UUID on stdout (empty line if none). Always exits 0.
# ---------------------------------------------------------------------------
find_workspace_uuid() {
  local workspace="$1" agent="$2" session_name="${3:-}"
  local abs; abs="$(mam_abs_workspace "$workspace")"
  MAM_STATE_JSON="$(load_state_json)" WS_ABS="$abs" AGENT="$agent" TARGET_SESSION="$session_name" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
from lib_py.workspace_uuid import find_workspace_uuid_main
find_workspace_uuid_main()
PYEOF
}

# ---------------------------------------------------------------------------
# capture_conversation_id <agent> <workdir> [session_name]
#
# Thin wrapper over find_workspace_uuid: resolves THIS workspace's conversation
# id (claude jsonl sessionId / agy db uuid) and prints it on stdout (empty line
# if none). find_workspace_uuid is already a workspace-scoped, 2-tier, race-free
# resolver (per-row own id -> workspace-scoped disk scan),
# so recording its result into the row before kill guarantees tier-1 on the next
# resume. Pass session_name to prefer that specific row's recorded id. Always exits 0.
# ---------------------------------------------------------------------------
capture_conversation_id() {
  local agent="$1" workdir="$2" session_name="${3:-}"
  find_workspace_uuid "$workdir" "$agent" "$session_name"
}

# ---------------------------------------------------------------------------
# Session isolation — Universal Global Config (Option B, P3-1)
#
# Config-home isolation (.mam/agent_homes/<uuid>/) and legacy isolation.root
# row consumers were completely deprecated and removed in favor of:
# 1. Universal Global Config: all agents read/write standard ~/.claude, ~/.gemini,
#    ~/.hermes user configuration and credential stores.
# 2. Process Isolation: each agent-workspace pair runs in its own herdr pane.
# 3. Conversation Isolation: session UUIDs discriminate conversation history.
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# is_already_stopped <session_name>
#
# Exits 0 if the row's status is 'stopped' (printing "stopped_at=<ts>" on
# stdout), 1 otherwise (including not-found). Used for idempotency: a second
# stop on an already-stopped session is a no-op.
# ---------------------------------------------------------------------------
is_already_stopped() {
  local session_name="$1"
  SESSION_NAME="$session_name" env_python "$AGENT_SESSIONS_YAML" <<'PYEOF'
import os, yaml, sqlite3, json
name = os.environ['SESSION_NAME']
yaml_path = os.environ['YAML_PATH']
db_path = os.path.splitext(yaml_path)[0] + '.db'
try:
    if os.path.exists(db_path):
        conn = sqlite3.connect(db_path, timeout=60.0)
        has_sessions_table = False
        try:
            row = conn.execute('SELECT status, data FROM sessions WHERE name=?', (name,)).fetchone()
            if row:
                status, s_data_str = row[0], row[1]
                if status == 'stopped':
                    s = json.loads(s_data_str)
                    print(f"stopped_at={s.get('stopped_at', '?')}")
                    raise SystemExit(0)
            has_sessions_table = True
        except sqlite3.OperationalError:
            pass
        if not has_sessions_table:
            row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
            if row:
                d = json.loads(row[0])
                for s in d.get('herdr_sessions', []):
                    if s.get('name') == name and s.get('status') == 'stopped':
                        print(f"stopped_at={s.get('stopped_at', '?')}")
                        raise SystemExit(0)
        conn.close()
        raise SystemExit(1)
    elif os.path.exists(yaml_path):
        with open(yaml_path) as f:
            d = yaml.safe_load(f) or {}
        for s in d.get('herdr_sessions', []):
            if s.get('name') == name and s.get('status') == 'stopped':
                print(f"stopped_at={s.get('stopped_at', '?')}")
                raise SystemExit(0)
except Exception:
    pass
raise SystemExit(1)
PYEOF
}

# ---------------------------------------------------------------------------
# multi-agent-mux-delegate-job integration helpers
#
# All paths are resolved relative to lib.sh's own location (BASH_SOURCE), so the
# skill tree is relocatable — no hardcoded absolute paths (review item 6).
# ---------------------------------------------------------------------------

# _delegate_py_bin — echo the virtualenv python (walk up from .agents/skills/), else python3.
_delegate_py_bin() {
  # Return cached result if available
  if [ -n "${AGENT_PYTHON_BIN:-}" ] && [ -x "$AGENT_PYTHON_BIN" ]; then
    printf '%s\n' "$AGENT_PYTHON_BIN"; return 0
  fi
  if [ -n "${VIRTUAL_ENV:-}" ] && [ -x "$VIRTUAL_ENV/bin/python" ]; then
    AGENT_PYTHON_BIN="$VIRTUAL_ENV/bin/python"
    printf '%s\n' "$AGENT_PYTHON_BIN"; return 0
  fi
  local d
  d="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  while [ "$d" != "/" ] && [ -n "$d" ]; do
    if [ -x "$d/.venv/bin/python" ]; then
      AGENT_PYTHON_BIN="$d/.venv/bin/python"
      printf '%s\n' "$AGENT_PYTHON_BIN"; return 0
    fi
    d="$(dirname "$d")"
  done
  AGENT_PYTHON_BIN="$(command -v python3 || echo python3)"
  printf '%s\n' "$AGENT_PYTHON_BIN"
}

# _delegate_script <name> — echo the path to a multi-agent-mux-delegate-job script, resolved
# relative to .agents/skills/ (lib.sh dir). Empty if not found.
_delegate_script() {
  local name="$1" skill_dir cand
  skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  cand="$skill_dir/multi-agent-mux-delegate-job/scripts/$name"
  if [ -f "$cand" ]; then printf '%s\n' "$cand"; return 0; fi
  printf '%s\n' "$(find "$skill_dir" -name "$name" 2>/dev/null | head -n 1 || true)"
}

# delegate_submit_job <prompt> <agent> <agent_session>
#
# Register a job in the multi-agent-mux-delegate-job registry. Prints the new JID on stdout.
delegate_submit_job() {
  local prompt="$1" agent="$2" session="$3"
  local py_bin registry_py
  py_bin="$(_delegate_py_bin)"
  registry_py="$(_delegate_script registry.py)"
  if [ -z "$registry_py" ] || [ ! -f "$registry_py" ]; then
    echo "ERROR: multi-agent-mux-delegate-job registry.py not found under .agents/skills/" >&2
    return 1
  fi
  "$py_bin" "$registry_py" register \
    --prompt "$prompt" \
    --agent "$agent" \
    --agent-session "$session"
}

# delegate_publish_event <job_id> <event> [detail]
#
# Publish a lifecycle event to the multi-agent-mux-delegate-job registry. Consolidates the
# inline .venv-walk + publish_event.py blocks that were duplicated across
# create/delete/resume (review item 7). Non-fatal by contract: an empty job id,
# a missing script, or a broker failure never aborts the caller.
delegate_publish_event() {
  local job_id="$1" event="$2" detail="${3:-}"
  [ -n "$job_id" ] || return 0
  local py_bin pub
  py_bin="$(_delegate_py_bin)"
  pub="$(_delegate_script publish_event.py)"
  [ -n "$pub" ] && [ -f "$pub" ] || return 0
  "$py_bin" "$pub" --job "$job_id" --event "$event" --detail "$detail" || true
}

# start_watchdog <job_id> [workdir]
# Spawns a watchdog process to monitor a delegate-job JOB in the background.
# The watchdog re-spawns the subscriber every 2 minutes (or whatever hard
# limit we set) and exits automatically when the JOB reaches terminal state.
# Returns the watchdog PID via stdout.
start_watchdog() {
  local job_id="$1"
  local workdir="${2:-$PWD}"
  local monitor_script="$workdir/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh"
  local log_file="$workdir/.mam/multi-agent-mux-monitor.log"
  
  if [ ! -f "$monitor_script" ]; then
    echo "ERROR: monitor script not found: $monitor_script" >&2
    return 1
  fi
  
  # Check if reconcile.sh --subscribe is already running on this workspace
  local pid
  pid=$(pgrep -f "bash $monitor_script --subscribe" || true)
  
  if [ -z "$pid" ]; then
    # Start the wildcard monitor subscriber daemon with --idle-timeout 0 (never idle out)
    # and ensure it runs with $workdir as cwd to anchor relative log paths.
    local orig_pwd="$PWD"
    cd "$workdir" || return 1
    nohup bash "$monitor_script" --subscribe --idle-timeout 0 </dev/null >> "$log_file" 2>&1 &
    pid=$!
    cd "$orig_pwd" || return 1
  fi
  
  echo "$pid"
}

# wait_for_tui_ready <session_name> <agent>
# Waits up to 30 seconds for the agent's TUI to render its welcome screen.
# Returns 0 on readiness, 1 on timeout, 2 on unobservable/headless.
wait_for_tui_ready() {
  local sess="$1" agent="$2"
  local strong_tokens="${MAM_STRONG_READY_TOKENS:-}"
  local weak_tokens="${MAM_WEAK_READY_TOKENS:-}"
  local placeholder="${MAM_INPUT_PLACEHOLDER:-}"
  local prompt="${MAM_INPUT_PROMPT:-}"
  local rule_pattern="${MAM_INPUT_RULE_PATTERN:-}"
  local modal_tokens="${MAM_MODAL_TOKENS:-}"

  # Self-contained resolution: fetch facts in 1 call if not already in environment
  if [ -z "$strong_tokens" ] && [ -z "$weak_tokens" ] && [ -z "${MAM_READY_TOKENS:-}" ]; then
    local _facts=""
    _facts="$("$(_delegate_py_bin)" -m lib_py.agents facts "$agent" 2>/dev/null)" || _facts=""
    if [ -n "$_facts" ]; then
      eval "$_facts"
      strong_tokens="${MAM_STRONG_READY_TOKENS:-}"
      weak_tokens="${MAM_WEAK_READY_TOKENS:-}"
      placeholder="${MAM_INPUT_PLACEHOLDER:-}"
      prompt="${MAM_INPUT_PROMPT:-}"
      rule_pattern="${MAM_INPUT_RULE_PATTERN:-}"
      modal_tokens="${MAM_MODAL_TOKENS:-}"
    fi
  fi
  # Fallback if strong_tokens is empty but MAM_READY_TOKENS is set
  if [ -z "$strong_tokens" ] && [ -n "${MAM_READY_TOKENS:-}" ]; then
    strong_tokens="$MAM_READY_TOKENS"
  fi

  if [ -z "$strong_tokens" ] && [ -z "$weak_tokens" ] && [ -z "$placeholder" ]; then
    echo "wait_for_tui_ready: no ready tokens for agent '$agent'" >&2
    return 1
  fi

  local empty_streak=0
  local empty_giveup="${MAM_READY_EMPTY_GIVEUP:-5}"
  local modal_pat="$_MAM_MODAL_TOKENS"
  [ -n "$modal_tokens" ] && modal_pat="${modal_pat}|${modal_tokens}"

  local i content tail_lines
  for i in {1..30}; do
    # 1. Single capture per iteration (T-3)
    content=$(_pane_capture "$sess")

    # Consecutive blank capture check (C-6)
    if [ -z "$content" ]; then
      empty_streak=$((empty_streak + 1))
      if [ "$empty_streak" -ge "$empty_giveup" ]; then
        echo "wait_for_tui_ready: unobservable/headless pane for '$sess' (consecutive empty captures: $empty_streak)" >&2
        return 2
      fi
      sleep 1
      continue
    fi
    empty_streak=0

    # 2. Check modal dialogs FIRST (F-2): if a blocking modal signature is present in the tail lines,
    # handle it and do NOT declare ready on this iteration.
    tail_lines=$(printf '%s\n' "$content" | grep -v '^[[:space:]]*$' | tail -n 20)
    if printf '%s\n' "$tail_lines" | grep -E -q "$modal_pat" 2>/dev/null; then
      handle_startup_dialogs "$sess" 1 || true
      sleep 1
      continue
    fi

    # 3. Tier 2 Readiness Evaluation (T-2b, T-4: Ready check first)
    local s_matched=false
    local w_matched=false
    local c_matched=false

    # S-layer: Strong ready token match
    if [ -n "$strong_tokens" ]; then
      if echo "$content" | grep -E -q "$strong_tokens" 2>/dev/null; then
        s_matched=true
      fi
    fi

    # S-layer: Placeholder match (must be >= 8 characters)
    if [ -n "$placeholder" ] && [ "${#placeholder}" -ge 8 ]; then
      if echo "$content" | grep -F -q "$placeholder" 2>/dev/null; then
        s_matched=true
      fi
    fi

    # W-layer: Weak ready token match
    if [ -n "$weak_tokens" ]; then
      if echo "$content" | grep -E -q "$weak_tokens" 2>/dev/null; then
        w_matched=true
      fi
    fi

    # C-layer: Corroboration (Prompt in last 5 non-blank lines OR rule pattern match)
    if [ -n "$prompt" ]; then
      if printf '%s\n' "$content" | grep -v '^[[:space:]]*$' | tail -n 5 | grep -F -q "$prompt" 2>/dev/null; then
        c_matched=true
      fi
    fi
    if [ -n "$rule_pattern" ]; then
      if echo "$content" | grep -E -q "$rule_pattern" 2>/dev/null; then
        c_matched=true
      fi
    fi

    # Decision: S OR (W AND C)
    if [ "$s_matched" = true ] || { [ "$w_matched" = true ] && [ "$c_matched" = true ]; }; then
      echo "✅ $agent TUI detected ready."
      return 0
    fi

    # 4. Startup dialog & hint resolution if not ready
    handle_startup_dialogs "$sess" 1 || true
    sleep 1
  done
  echo "⚠️ TUI readiness check timed out for '$sess'." >&2
  return 1
}

# inject_instructions <session_name> <instructions> [job_id]
# Injects instructions into the herdr session using paste-buffer and C-m.
# Delegates to send_keys_safe to ensure focus and renderer correctness (MS-5).
inject_instructions() {
  send_keys_safe "$1" "$2" "${3:-onboard}"
}

# ---------------------------------------------------------------------------
# Prompt-lock safe delivery (FW-W2). Keys are sent on evidence, not timers.
# send_keys_safe returns 0 only if the text was verifiably submitted.
# Exit codes: 1=pane never quiesced  2=dialog blocking input
#             3=paste not visible    4=Enter not accepted
# Callers MUST handle non-zero.
# ---------------------------------------------------------------------------

# Server-aware herdr (same isolation rule as inject_instructions).
_sks_herdr() {
  mam_herdr "$@"
}

_pane_capture() {
  local raw
  raw="$(_sks_herdr capture-pane -p -t "$1" 2>/dev/null || echo "")"
  if [ -z "$raw" ]; then
    echo ""
    return 0
  fi
  python3 -c '
import sys, json
raw = sys.stdin.read()
try:
    data = json.loads(raw)
    text = data.get("result", {}).get("read", {}).get("text")
    if text is not None:
        print(text, end="")
    else:
        print(raw, end="")
except Exception:
    print(raw, end="")
' <<< "$raw"
}

# Bottom-N *content* lines: capture-pane -p pads the viewport with trailing
# blank rows; window on non-blank lines or top-anchored dialogs are invisible.
_pane_tail() { _pane_capture "$1" | grep -v '^[[:space:]]*$' | tail -n "${2:-20}"; }

# _wait_session_gone <sess> [max_sec=5]
# Reactive loop to wait until a herdr session goes away (happy path returns early).
_wait_session_gone() {
  local sess="$1" max="${2:-5}" i
  for ((i = 0; i < max * 4; i++)); do
    _sks_herdr has-session -t "$sess" 2>/dev/null || return 0
    sleep 0.25
  done
  return 1
}

# _pane_quiescent <sess> [tries=20] [interval=0.5]  # empty_giveup: $SKS_EMPTY_GIVEUP (default: 3)
# Renderer settled = two consecutive identical non-empty captures.
# Defeats RC-A (Blessed/Ink renderer bottleneck) without a magic fixed sleep.
# Returns 0 if renderer settled (two identical non-empty captures).
# Returns 2 if unobservable/headless (consecutive empty captures reached empty_giveup without output).
# Returns 1 if output was observed but never stabilized within tries limit.
_pane_quiescent() {
  local sess="$1" tries="${2:-20}" interval="${3:-0.5}" prev="__none__" cur i
  local saw_output=0 empty_streak=0
  local empty_giveup="${SKS_EMPTY_GIVEUP:-3}"
  for ((i = 0; i < tries; i++)); do
    cur=$(_pane_capture "$sess")
    if [ -z "$cur" ]; then
      empty_streak=$((empty_streak + 1))
      [ "$saw_output" = "0" ] && [ "$empty_streak" -ge "$empty_giveup" ] && return 2
      sleep "$interval"
      continue
    fi
    saw_output=1
    empty_streak=0
    [ "$cur" = "$prev" ] && return 0
    prev="$cur"
    sleep "$interval"
  done
  [ "$saw_output" = "0" ] && return 2
  return 1
}

# _pane_dialog_open <sess> — focus-stealing modal signatures (trust /
# permission / OAuth / list-selection), checked in the bottom 8 pane lines
# only (dialogs render near the input area; conversation text above must not
# trigger this). Tokens must NOT appear on normal idle prompt screens.
_pane_dialog_open() {
  local extra="${MAM_MODAL_TOKENS:-}"
  local pat="$_MAM_MODAL_TOKENS"
  [ -n "$extra" ] && pat="${pat}|${extra}"
  _pane_tail "$1" "${MAM_DIALOG_TAIL_LINES:-8}" | grep -Eq "$pat"
}

# send_keys_safe <sess> <text> [job_id]
# 1. Wait for renderer quiescence (RC-A). If unobservable (headless), bypass visual checks.
# 2. Refuse to paste while a dialog is open (RC-B/RC-C): wait up to
#    SKS_DIALOG_TIMEOUT (default 30 s); if SKS_DIALOG_ESCAPE=1, send a single
#    Escape per poll and re-check. NEVER a blind Enter.
# 3. Native herdr 0.8+ RPC fast path: agent prompt handles atomic text + enter submission.
# 4. Paste via unique buffer; verify the text landed (marker visible).
# 5. Submit C-m; verify submission (marker left the input area AND the pane
#    changed); retry up to 3 times.
send_keys_safe() {
  local sess="$1" text="$2" job_id="${3:-adhoc}"
  local pre_submit deadline try
  local MAM_MODAL_TOKENS="${MAM_MODAL_TOKENS:-}"
  local MAM_AGENT_NAME MAM_OWN_KEY MAM_INPUT_PROMPT MAM_INPUT_PLACEHOLDER MAM_INPUT_RULE_PATTERN
  local MAM_READY_TOKENS MAM_STRONG_READY_TOKENS MAM_WEAK_READY_TOKENS MAM_EXIT_KEY MAM_DELEGATE_AGENT_KEY

  if [ -z "${MAM_MODAL_TOKENS:-}" ]; then
    local _sks_agent=""
    case "$sess" in
      *-claude|*-claude-[0-9]*|claude|claude-[0-9]*) _sks_agent="claude" ;;
      *-agy|*-agy-[0-9]*|agy|agy-[0-9]*)          _sks_agent="agy" ;;
      *-hermes|*-hermes-[0-9]*|hermes|hermes-[0-9]*) _sks_agent="hermes" ;;
      *-grok|*-grok-[0-9]*|grok|grok-[0-9]*)       _sks_agent="grok" ;;
      *-opencode|*-opencode-[0-9]*|opencode|opencode-[0-9]*) _sks_agent="opencode" ;;
    esac
    if [ -n "$_sks_agent" ]; then
      eval "$("$(_delegate_py_bin)" -m lib_py.agents facts "$_sks_agent" 2>/dev/null || true)"
    fi
  fi

  local _q_rc=0
  _pane_quiescent "$sess" "${SKS_QUIESCENT_TRIES:-20}" "${SKS_QUIESCENT_INTERVAL:-0.5}" || _q_rc=$?
  if [ "$_q_rc" = "1" ]; then
    echo "send_keys_safe: pane never quiesced ($sess)" >&2
    return 1
  fi

  if [ "$_q_rc" != "2" ]; then
    deadline=$(( $(date +%s) + ${SKS_DIALOG_TIMEOUT:-30} ))
    while _pane_dialog_open "$sess"; do
      if [ "${SKS_DIALOG_ESCAPE:-0}" = "1" ]; then
        _sks_herdr send-keys -t "$sess" Escape
        sleep 1
      fi
      if [ "$(date +%s)" -ge "$deadline" ]; then
        echo "send_keys_safe: dialog blocking input ($sess)" >&2
        return 2
      fi
      sleep 2
    done
  fi

  local agent_target
  agent_target=$(_sanitize_herdr_agent_name "$sess")
  # Native herdr 0.8+ fast path: agent prompt handles atomic text + enter submission
  # Gated behind quiescence and dialog checks; returns 0 on RPC success to prevent duplicate input
  if _sks_herdr agent prompt "$agent_target" "$text" >/dev/null 2>&1 || _sks_herdr agent prompt "$sess" "$text" >/dev/null 2>&1; then
    return 0
  fi

  # Fallback: paste buffer submission. Compute verification markers on demand.
  local marker marker_norm
  # Verification token: last 24 *characters* (not bytes — `tail -c` can split a
  # multi-byte UTF-8 char, e.g. Korean, producing a marker that can never match
  # the properly-decoded rendered pane text) of the last non-empty line.
  marker=$(printf '%s' "$text" | tr -d '\r' | awk 'NF {line=$0} END {print line}' | python3 -c "import sys; print(sys.stdin.read().rstrip('\n')[-24:], end='')")
  # Whitespace-normalized copy for matching: some TUIs don't just soft-wrap
  # with a bare newline — they add a leading-space "hanging indent" on the
  # continuation line too, so removing only '\n' still leaves an extra space
  # that breaks an exact literal match. Matching with all whitespace collapsed
  # out sidesteps wrap formatting entirely, whatever shape it takes.
  marker_norm=$(printf '%s' "$marker" | tr -d '[:space:]')

  local sks_buf="sks_${sess}_${job_id}_$$_${RANDOM}_$(date +%s%N 2>/dev/null || date +%s)"
  _sks_herdr set-buffer -b "$sks_buf" "$text"
  local _paste_rc=0
  _sks_herdr paste-buffer -b "$sks_buf" -t "$sess" || _paste_rc=$?
  _sks_herdr delete-buffer -b "$sks_buf" 2>/dev/null || true
  if [ "$_paste_rc" != "0" ]; then
    echo "send_keys_safe: paste-buffer failed rc=$_paste_rc ($sess)" >&2
    return 3
  fi
  local was_popup=0
  if [[ "$sess" =~ "claude" ]] || [[ "$sess" =~ "agy" ]] || [[ "$sess" =~ "grok" ]]; then
    # Skip strict paste check due to scrollout false-positives, proceed to C-m submission loop
    true
  else
    sleep 0.5
    local pane_content
    pane_content=$(_pane_capture "$sess")
    if printf '%s\n' "$pane_content" | grep -Eq "Pasted text|paste again to expand"; then
      was_popup=1
    # Strip ALL whitespace before matching — the rendered pane soft-wraps long
    # lines at the terminal width (and some TUIs add indentation on the
    # continuation line too), which can split/reformat the marker across two
    # visual lines and make a literal grep miss it even though the paste landed.
    elif ! printf '%s' "$pane_content" | tr -d '[:space:]' | grep -Fq "$marker_norm"; then
      echo "send_keys_safe: paste not visible ($sess)" >&2
      return 3
    fi
  fi

  for try in 1 2 3; do
    pre_submit=$(_pane_capture "$sess")
    _sks_herdr send-keys -t "$sess" C-m
    sleep "$try"
    local cur_content
    cur_content=$(_pane_capture "$sess")
    # Hardened Submission Checks
    if printf '%s\n' "$cur_content" | grep -Fq "esc to interrupt" || printf '%s\n' "$cur_content" | grep -Eq "● |✽ |[A-Za-z]+ing"; then
      return 0
    fi
    if [ "$was_popup" = "0" ] && ! _pane_tail "$sess" 3 | tr -d '[:space:]' | grep -Fq "$marker_norm" && [ "$cur_content" != "$pre_submit" ]; then
      return 0
    elif [ "$was_popup" = "1" ] && \
         ! printf '%s\n' "$cur_content" | grep -Eq "Pasted text|paste again to expand" && \
         [ "$cur_content" != "$pre_submit" ]; then
      return 0
    fi
  done
  echo "send_keys_safe: Enter not accepted after 3 tries ($sess)" >&2
  return 4
}

# handle_startup_dialogs <sess> [timeout_sec=20]
# Post-start/resume dialog policy for claude: accept the trust / bypass
# dialogs ONLY when their signature is positively on screen; return as soon
# as the TUI banner is ready. Replaces the blind Enter/Down/Enter sequence.
handle_startup_dialogs() {
  local sess="$1" timeout="${2:-20}" waited=0 pane
  while [ "$waited" -lt "$timeout" ]; do
    pane=$(_pane_tail "$sess" 20)
    if printf '%s\n' "$pane" | grep -Eq 'Do you trust the files|Yes, I trust this folder|Quick safety check'; then
      _sks_herdr send-keys -t "$sess" Enter
    elif printf '%s\n' "$pane" | grep -q 'Yes, try it'; then
      # Fullscreen-renderer upsell modal (not the idle /tui tip). Enter would
      # accept and restart the session without permission flags — reject.
      _sks_herdr send-keys -t "$sess" Escape
    elif printf '%s\n' "$pane" | grep -q 'Yes, proceed'; then
      _sks_herdr send-keys -t "$sess" Down
      sleep 0.3
      _sks_herdr send-keys -t "$sess" Enter
    elif printf '%s\n' "$pane" | grep -q 'Resuming the full session'; then
      _sks_herdr send-keys -t "$sess" Enter
    elif printf '%s\n' "$pane" | grep -q 'Press Enter to continue'; then
      _sks_herdr send-keys -t "$sess" Enter
    elif printf '%s\n' "$pane" | grep -Eq "${_MAM_READY_TOKENS_CLAUDE:-Anthropic|Assistant|Chat|Welcome}"; then
      return 0
    fi
    sleep 1
    waited=$((waited + 1))
  done
  return 0
}

# Auto-initialize the herdr translation shim wrapper on library source
_init_herdr_isolation
