#!/usr/bin/env bash
# multi-agent-mux-delegate-job — user-facing orchestrator for the multi-agent-mux-delegate-job skill.
#
# Subcommands:
#   submit   register a job, start the subscriber FIRST, then run the agent,
#            then (optionally) run a validation script.
#   status   show one job record.
#   list     list all jobs.
#   verify   run a user-supplied --validate script against a job's artifacts.
#   wait     block until all running/pending jobs reach a terminal state.
#
# This is a reference wrapper: it shells out to the python scripts that live
# next to it. Copy it into your project and customise as needed. It never hard
# fails if `claude`/`codex`/`herdr` are missing — it prints what it would run.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Load local env file (.mam.env preferred, .env fallback) from workspace root or explicit MAM_ENV_FILE
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
TARGET_ENV="${MAM_ENV_FILE:-}"
if [[ -z "$TARGET_ENV" ]]; then
  if [[ -f "$REPO_ROOT/.mam.env" ]]; then
    TARGET_ENV="$REPO_ROOT/.mam.env"
  elif [[ -f "$REPO_ROOT/.env" ]]; then
    TARGET_ENV="$REPO_ROOT/.env"
    echo "WARNING: Loading deprecated config file '$TARGET_ENV'. Please migrate to '.mam.env'." >&2
  elif [[ -f .mam.env ]]; then
    TARGET_ENV=".mam.env"
    echo "WARNING: Loading config from cwd relative path '$TARGET_ENV'." >&2
  elif [[ -f .env ]]; then
    TARGET_ENV=".env"
    echo "WARNING: Loading deprecated config from cwd relative path '$TARGET_ENV'. Please migrate to '.mam.env'." >&2
  fi
fi

if [[ -n "$TARGET_ENV" && -f "$TARGET_ENV" ]]; then
  set -a; source "$TARGET_ENV"; set +a
fi

# Source EARLY (before any herdr usage in run_agent) — this is what turns
# plain `herdr` into the tmux-compat shim (herdr() function) and provides
# resolve_herdr_session/send_keys_safe. Sourcing it late meant the
# has-session pre-flight check below used to hit the real herdr binary with
# a nonexistent subcommand and always fail.
source "$SCRIPT_DIR/../lib.sh"

# Pick an interpreter: prefer a project .venv, else python3.
pick_python() {
  local py_bin
  if [[ -n "${DELEGATE_JOB_PYTHON:-}" ]]; then
    py_bin="$DELEGATE_JOB_PYTHON"
  elif [[ -n "${AGENT_PYTHON_BIN:-}" ]] && [[ -x "$AGENT_PYTHON_BIN" ]]; then
    py_bin="$AGENT_PYTHON_BIN"
  elif [[ -x "${WORKDIR:-.}/.venv/bin/python" ]]; then
    py_bin="${WORKDIR}/.venv/bin/python"
  elif [[ -x ".venv/bin/python" ]]; then
    py_bin="$(pwd)/.venv/bin/python"
  else
    py_bin="python3"
  fi
  if ! "$py_bin" -c "import paho.mqtt" 2>/dev/null; then
    echo "ERROR: paho-mqtt package is missing for $py_bin." >&2
    echo "       Please create a virtual environment and install it:" >&2
    echo "       python3 -m venv .venv && .venv/bin/pip install -r \"$SCRIPT_DIR/requirements.txt\"" >&2
    exit 1
  fi
  echo "$py_bin"
}

REGISTRY_DIR_DEFAULT=".mam/jobs"

usage() {
  cat <<'EOF'
multi-agent-mux-delegate-job <command> [options]

  submit  --agent <name> --prompt <text> [--workdir <dir>] [--agent-session <label>]
          [--timeout <sec>] [--idle-timeout <sec>] [--validate <script>]
          [--registry-dir <dir>] [--dry-run] [--role <role_name>]
          [--type <direct|loop|discuss>] [--reviewer <reviewer_agent>]
          [--reviewer-session <reviewer_session>] [--max-iterations <count>]
          [--counterpart-role <role_name>] [--strict-role-check]
          # The skill is herdr-interactive only; --mode print was removed.
  status  --job <id> [--registry-dir <dir>]
  list    [--registry-dir <dir>]
  verify  --job <id> --validate <script> [--registry-dir <dir>]
  wait    [--job <id>] [--timeout <sec>] [--registry-dir <dir>]
  logs    <job_id> | --list      # persistent audit log (delegate_job_logs/)
EOF
}

# ---- arg parsing helpers --------------------------------------------------
AGENT="claude-code"; PROMPT=""; WORKDIR="$(pwd)"; AGENT_SESSION="herdr:claude"
TIMEOUT=3600; IDLE_TIMEOUT=120; VALIDATE=""; DRY_RUN=0
JOB_ID=""; REGISTRY_DIR="$REGISTRY_DIR_DEFAULT"; DELEGATE_ROLE="Worker"
TYPE="direct"; REVIEWER="hermes"; REVIEWER_SESSION="herdr:hermes"; MAX_ITERATIONS=5
DEFAULT_COUNTERPART_ROLE="Reviewer"
COUNTERPART_ROLE="$DEFAULT_COUNTERPART_ROLE"
STRICT_ROLE_CHECK=0
ROLE_ALIASES_JSON='{"worker": ["worker", "creator"], "planner": ["planner"], "reviewer": ["reviewer"]}'
COUNTERPART_ROLE_EXPLICIT=0

parse_opts() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --agent) AGENT="$2"; shift 2;;
      --prompt) PROMPT="$2"; shift 2;;
      --workdir) WORKDIR="$2"; shift 2;;
      --agent-session) AGENT_SESSION="$2"; shift 2;;
      --timeout) TIMEOUT="$2"; shift 2;;
      --idle-timeout) IDLE_TIMEOUT="$2"; shift 2;;
      --validate) VALIDATE="$2"; shift 2;;
      --job) JOB_ID="$2"; shift 2;;
      --registry-dir) REGISTRY_DIR="$2"; shift 2;;
      --dry-run) DRY_RUN=1; shift;;
      --role) DELEGATE_ROLE="$2"; shift 2;;
      --type) TYPE="$2"; shift 2;;
      --reviewer) REVIEWER="$2"; shift 2;;
      --reviewer-session) REVIEWER_SESSION="$2"; shift 2;;
      --max-iterations) MAX_ITERATIONS="$2"; shift 2;;
      --counterpart-role) COUNTERPART_ROLE="$2"; COUNTERPART_ROLE_EXPLICIT=1; shift 2;;
      --strict-role-check) STRICT_ROLE_CHECK=1; shift;;
      *) echo "unknown option: $1" >&2; usage; exit 1;;
    esac
  done
}

_to_lower() {
  printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

cmd_submit() {
  parse_opts "$@"
  [[ -n "$PROMPT" ]] || { echo "submit requires --prompt" >&2; exit 1; }
  PY="$(pick_python)"
  cd "$WORKDIR"
  mkdir -p "$REGISTRY_DIR"

  # 1) register job (prints the new job id)
  JOB_ID="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" register \
      --prompt "$PROMPT" --agent "$AGENT" --agent-session "$AGENT_SESSION" --role "$DELEGATE_ROLE" \
      --timeout "$TIMEOUT" --idle-timeout "$IDLE_TIMEOUT" \
      --job-type "$TYPE" --reviewer "$REVIEWER" --reviewer-session "$REVIEWER_SESSION" \
      --max-iterations "$MAX_ITERATIONS")"
  echo "registered job: $JOB_ID"

  # 1-1) Provision job directory and write direct brief.md (MAM Job Restructuring)
  local job_dir="$REGISTRY_DIR/$JOB_ID"
  if [[ "$DRY_RUN" != "1" ]]; then
    mkdir -p "$job_dir"
    cat <<EOF > "$job_dir/brief.md"
# 📋 Brief: Job $JOB_ID Delegation

- **Job ID**: $JOB_ID
- **Target Agent**: $AGENT (session: $AGENT_SESSION)
- **Role**: $DELEGATE_ROLE
- **Timeout**: $TIMEOUT s (Idle: $IDLE_TIMEOUT s)
- **Output Report Path**: .mam/jobs/$JOB_ID/$AGENT-reports/report-final.md

## 🔎 Task Description
$PROMPT
EOF
    echo "provisioned job directory: $job_dir"
  fi

  local START_TIME
  START_TIME=$(date +%s)

  if [[ "$TYPE" == "direct" ]]; then
    # 1) Read dispatch token and attempt id for this dispatch
    local _disp_tok=""
    local _attempt_id=""
    _disp_tok="$("$PY" -c "import json, os; p=os.path.join('$REGISTRY_DIR', '$JOB_ID.json'); print(json.load(open(p)).get('dispatch_token','') or '') if os.path.exists(p) else print('')" 2>/dev/null || true)"
    _attempt_id="$("$PY" -c "import json, os; p=os.path.join('$REGISTRY_DIR', '$JOB_ID.json'); print(json.load(open(p)).get('attempt_id','') or '') if os.path.exists(p) else print('')" 2>/dev/null || true)"
    local pub_extra=()
    [ -n "$_disp_tok" ] && pub_extra+=(--expect-dispatch-token "$_disp_tok")
    [ -n "$_attempt_id" ] && pub_extra+=(--attempt-id "$_attempt_id")
    local pub="$PY $SCRIPT_DIR/scripts/publish_event.py --registry-dir $REGISTRY_DIR --job $JOB_ID ${pub_extra[*]}"
    local instructions="Your job_id is \"$JOB_ID\". Detailed task requirements, instructions, and target output paths are documented in the task brief file at: .mam/jobs/$JOB_ID/brief.md. Please READ and follow .mam/jobs/$JOB_ID/brief.md to complete your work. Commands: start='$pub --event started', success='$pub --event completed --detail <summary>', error='$pub --event error --detail <reason>'."

    if [[ "$DRY_RUN" == "1" ]]; then
      run_agent "$JOB_ID" "$instructions"
      local logs_root_dry="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
      echo "$logs_root_dry/$JOB_ID"
      return 0
    fi

    # 1) Preflight
    if ! preflight_agent "$JOB_ID" "$AGENT_SESSION" 0; then
      finalize_unfinished "$JOB_ID" "error" 1 "preflight"
      local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
      echo "$logs_root/$JOB_ID"
      exit 5
    fi

    # 2) TUI idle handshake
    if [ "$AGENT" != "human" ]; then
      local idle_rc=0
      wait_for_agent_idle "$AGENT_SESSION" || idle_rc=$?
      export _SKS_IDLE_RC="$idle_rc"
      if [ $idle_rc -eq 1 ] || [ $idle_rc -eq 2 ]; then
        finalize_unfinished "$JOB_ID" "error" "$idle_rc" "idle_wait"
        local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
        echo "$logs_root/$JOB_ID"
        exit 5
      fi
    fi

    # 3) START THE SUBSCRIBER
    local logf="$REGISTRY_DIR/$JOB_ID.subscriber.out"
    local sub_extra_args=()
    [ -n "$_disp_tok" ] && sub_extra_args+=(--expect-dispatch-token "$_disp_tok")
    "$PY" "$SCRIPT_DIR/scripts/job_subscriber.py" --registry-dir "$REGISTRY_DIR" \
        --job "$JOB_ID" --timeout "$TIMEOUT" --idle-timeout "$IDLE_TIMEOUT" \
        ${sub_extra_args[@]+"${sub_extra_args[@]}"} \
        >"$logf" 2>&1 &
    local sub_pid=$!
    echo "subscriber pid: $sub_pid (log: $logf)"
    # Wait for the subscriber to CONNACK + SUBSCRIBE (reactive handshake)
    local sub_ready=0
    for ((i = 0; i < 25; i++)); do
      if kill -0 "$sub_pid" 2>/dev/null; then
        if grep -q '^SUBSCRIBED ' "$logf" 2>/dev/null; then
          sub_ready=1
          break
        fi
      else
        local sub_exit=0
        wait "$sub_pid" 2>/dev/null || sub_exit=$?
        if [ $sub_exit -eq 0 ] || [ $sub_exit -eq 3 ]; then
          sub_ready=1
          break
        else
          echo "ERROR: subscriber died early (pid=$sub_pid, exit=$sub_exit, check $logf)" >&2
          local mapped_st
          mapped_st="$(_map_sub_rc "$sub_exit" "$JOB_ID")"
          finalize_unfinished "$JOB_ID" "$mapped_st" "$sub_exit" "subscriber"
          mapped_st="${FINAL_JOB_STATUS:-$mapped_st}"
          local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
          echo "$logs_root/$JOB_ID"
          if [ "${MAM_DELEGATE_FAIL_CLOSED:-1}" = "0" ]; then
            exit 0
          fi
          exit "$(_rc_for "$mapped_st")"
        fi
      fi
      sleep 0.2
    done
    if [ "$sub_ready" -ne 1 ]; then
      echo "WARNING: subscriber subscribe handshake timed out — falling back to proceed" >&2
    fi

    # 5) Inject agent
    if ! inject_agent "$JOB_ID" "$instructions" "$AGENT_SESSION"; then
      reap_subscriber "$sub_pid"
      finalize_unfinished "$JOB_ID" "error" 1 "inject"
      local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
      echo "$logs_root/$JOB_ID"
      exit 1
    fi

    # 6) optional validation hook
    if [[ -n "$VALIDATE" ]]; then
      echo "running validation: $VALIDATE"
      if JOB_ID="$JOB_ID" REGISTRY_DIR="$REGISTRY_DIR" bash "$VALIDATE"; then
        echo "validation: PASS"
      else
        local rc=$?
        echo "validation: FAIL (exit $rc)"
      fi
    fi

    local sub_rc=0
    wait "$sub_pid" || sub_rc=$?
    echo "subscriber output:"; cat "$logf" || true

    local job_status
    job_status="$(_map_sub_rc "$sub_rc" "$JOB_ID")"

    finalize_unfinished "$JOB_ID" "$job_status" "$sub_rc" "direct"
    job_status="${FINAL_JOB_STATUS:-$job_status}"

    # Last stdout line: the persistent audit-log dir for this job (see SKILL.md
    # "Audit Logs"). Callers can scrape `tail -n1` to find it.
    local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
    echo "$logs_root/$JOB_ID"
    if [ "${MAM_DELEGATE_FAIL_CLOSED:-1}" = "0" ]; then
      exit 0
    fi
    exit "$(_rc_for "$job_status")"
  else
    # Implement loop/discuss orchestrator
    local iteration=1
    local current_prompt="$PROMPT"
    local current_session="$AGENT_SESSION"
    local _phase="worker"
    local display_role="$DELEGATE_ROLE"
    local attempt=0
    local final_loop_rc=0

    if [[ "$DRY_RUN" == "1" ]]; then
      echo "[dry-run] orchestrator loop would start for job: $JOB_ID type: $TYPE"
      echo "worker session: $AGENT_SESSION, reviewer session: $REVIEWER_SESSION"
      local logs_root_dry="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
      echo "$logs_root_dry/$JOB_ID"
      return 0
    fi

    while true; do
      echo "=================================================="
      echo "Iteration $iteration - Role: $display_role"
      echo "Session: $current_session"
      echo "=================================================="

      # 1-1) Provision job directory and write iteration brief.md (MAM Job Restructuring)
      local job_dir="$REGISTRY_DIR/$JOB_ID"
      local clean_session="${current_session#herdr:}"
      mkdir -p "$job_dir"
      cat <<EOF > "$job_dir/brief.md"
# 📋 Brief: Job $JOB_ID Delegation (Iteration $iteration)
 
- **Job ID**: $JOB_ID
- **Target Agent/Session**: $current_session
- **Role**: $display_role
- **Iteration**: $iteration
- **Output Report Path**: .mam/jobs/$JOB_ID/${clean_session}-reports/report-final.md
 
## 🔎 Task Description
$current_prompt
EOF
      echo "provisioned iteration brief: $job_dir/brief.md"

      attempt=$((attempt + 1))

      local force_warn_only=0
      local cp_lower def_lower
      cp_lower=$(_to_lower "$COUNTERPART_ROLE")
      def_lower=$(_to_lower "$DEFAULT_COUNTERPART_ROLE")
      if [[ "$_phase" == "reviewer" && "$COUNTERPART_ROLE_EXPLICIT" -eq 1 \
            && "$cp_lower" != "$def_lower" ]]; then
        force_warn_only=1
      fi

      # Preflight
      if ! preflight_agent "$JOB_ID" "$current_session" "$force_warn_only"; then
        finalize_unfinished "$JOB_ID" "error" 1 "preflight"
        local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
        echo "$logs_root/$JOB_ID"
        exit 5
      fi

      # Idle wait
      local cur_agent="$AGENT"
      [ "$_phase" = "reviewer" ] && cur_agent="$REVIEWER"
      if [ "$cur_agent" != "human" ]; then
        local idle_rc=0
        wait_for_agent_idle "$current_session" || idle_rc=$?
        export _SKS_IDLE_RC="$idle_rc"
        if [ $idle_rc -eq 1 ] || [ $idle_rc -eq 2 ]; then
          finalize_unfinished "$JOB_ID" "error" "$idle_rc" "idle_wait"
          local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
          echo "$logs_root/$JOB_ID"
          exit 5
        fi
      fi

      # Update job details in registry
      "$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" update \
          --job "$JOB_ID" \
          --agent-session "$current_session" \
          --prompt "$current_prompt" \
          --iteration "$iteration" \
          --attempt-id "$attempt" \
          --role "$display_role" \
          --status "pending"

      local _disp_tok=""
      _disp_tok="$("$PY" -c "import json, os; p=os.path.join('$REGISTRY_DIR', '$JOB_ID.json'); print(json.load(open(p)).get('dispatch_token','') or '') if os.path.exists(p) else print('')" 2>/dev/null || true)"

      # Start subscriber
      local logf="$REGISTRY_DIR/${JOB_ID}.iter_${iteration}_${display_role}.subscriber.out"
      local loop_sub_args=(--attempt-id "$attempt")
      [ -n "$_disp_tok" ] && loop_sub_args+=(--expect-dispatch-token "$_disp_tok")
      "$PY" "$SCRIPT_DIR/scripts/job_subscriber.py" --registry-dir "$REGISTRY_DIR" \
          --job "$JOB_ID" --timeout "$TIMEOUT" --idle-timeout "$IDLE_TIMEOUT" \
          "${loop_sub_args[@]}" \
          >"$logf" 2>&1 &
      local sub_pid=$!
      echo "subscriber pid: $sub_pid (log: $logf)"
      # Wait for the subscriber to CONNACK + SUBSCRIBE (reactive handshake)
      local sub_ready=0
      for ((i = 0; i < 25; i++)); do
        if kill -0 "$sub_pid" 2>/dev/null; then
          if grep -q '^SUBSCRIBED ' "$logf" 2>/dev/null; then
            sub_ready=1
            break
          fi
        else
          local sub_exit=0
          wait "$sub_pid" 2>/dev/null || sub_exit=$?
          if [ $sub_exit -eq 0 ] || [ $sub_exit -eq 3 ]; then
            sub_ready=1
            break
          else
            echo "ERROR: subscriber died early (pid=$sub_pid, exit=$sub_exit, check $logf)" >&2
            local mapped_st
            mapped_st="$(_map_sub_rc "$sub_exit" "$JOB_ID")"
            finalize_unfinished "$JOB_ID" "$mapped_st" "$sub_exit" "subscriber"
            mapped_st="${FINAL_JOB_STATUS:-$mapped_st}"
            local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
            echo "$logs_root/$JOB_ID"
            if [ "${MAM_DELEGATE_FAIL_CLOSED:-1}" = "0" ]; then
              exit 0
            fi
            exit "$(_rc_for "$mapped_st")"
          fi
        fi
        sleep 0.2
      done
      if [ "$sub_ready" -ne 1 ]; then
        echo "WARNING: subscriber subscribe handshake timed out — falling back to proceed" >&2
      fi

      # Format instruction block
      local loop_pub_extra=(--attempt-id "$attempt")
      [ -n "$_disp_tok" ] && loop_pub_extra+=(--expect-dispatch-token "$_disp_tok")
      local pub="$PY $SCRIPT_DIR/scripts/publish_event.py --registry-dir $REGISTRY_DIR --job $JOB_ID ${loop_pub_extra[*]}"
      local instructions="Your job_id is \"$JOB_ID\". Detailed task requirements, instructions, and target output paths for iteration $iteration are documented in the task brief file at: .mam/jobs/$JOB_ID/brief.md. Please READ and follow .mam/jobs/$JOB_ID/brief.md to complete your work. Commands: start='$pub --event started', success='$pub --event completed --detail <summary>', error='$pub --event error --detail <reason>'."

      # Trigger agent
      if ! inject_agent "$JOB_ID" "$instructions" "$current_session"; then
        reap_subscriber "$sub_pid"
        finalize_unfinished "$JOB_ID" "error" 1 "inject"
        local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
        echo "$logs_root/$JOB_ID"
        exit 1
      fi

      # Wait for subscriber
      local sub_rc=0
      wait "$sub_pid" || sub_rc=$?
      echo "subscriber output:"; cat "$logf" || true

      local job_status
      job_status="$(_map_sub_rc "$sub_rc" "$JOB_ID")"
      
      echo "Job role $display_role finished with status: $job_status"

      # Retrieve feedback from the last event
      local feedback
      feedback="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" get-feedback --job "$JOB_ID")"
      echo "Feedback/Detail: $feedback"
      if [[ "$_phase" == "worker" ]]; then
        if [[ "$job_status" != "completed" ]]; then
          echo "Worker did not complete successfully (status: $job_status). Terminating workflow."
          finalize_unfinished "$JOB_ID" "$job_status" "$sub_rc" "worker" "$current_session"
          job_status="${FINAL_JOB_STATUS:-$job_status}"
          if [[ "$job_status" == "completed" ]]; then
            echo "Worker completed during grace period. Continuing to reviewer."
            feedback="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" get-feedback --job "$JOB_ID")"
          else
            local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
            echo "$logs_root/$JOB_ID"
            if [ "${MAM_DELEGATE_FAIL_CLOSED:-1}" = "0" ]; then
              exit 0
            fi
            exit "$(_rc_for "$job_status")"
          fi
        fi

        # Worker completed successfully, now switch to reviewer
        _phase="reviewer"
        display_role="$COUNTERPART_ROLE"
        current_session="$REVIEWER_SESSION"
        # Build reviewer prompt based on type
        if [[ "$TYPE" == "loop" ]]; then
          current_prompt="Review the changes/artifacts generated for job $JOB_ID. Check if they meet the requirements. If correct, publish completed event with 'PASS'. If there are issues, publish error event with detailed feedback/nits. CRITICAL: When raising issues or giving a review, you MUST include the exact reason for the issue and a clear direction for improvement (문제 제시에 대한 이유와 확실한 개선 방향을 반드시 포함해야 합니다)."
        elif [[ "$TYPE" == "discuss" ]]; then
          current_prompt="Read draft/documents generated for job $JOB_ID. Review the feasibility and content. Write your feedback/objections. If you agree with the plan, reply with 'AGREE'."
        fi
      else
        if [[ "$job_status" != "completed" ]]; then
          echo "Reviewer did not complete successfully (status: $job_status). Terminating workflow."
          finalize_unfinished "$JOB_ID" "$job_status" "$sub_rc" "reviewer" "$current_session"
          job_status="${FINAL_JOB_STATUS:-$job_status}"
          if [[ "$job_status" == "completed" ]]; then
            echo "Reviewer completed during grace period. Continuing evaluation."
            feedback="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" get-feedback --job "$JOB_ID")"
          else
            local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
            echo "$logs_root/$JOB_ID"
            if [ "${MAM_DELEGATE_FAIL_CLOSED:-1}" = "0" ]; then
              exit 0
            fi
            exit "$(_rc_for "$job_status")"
          fi
        fi

        # Reviewer finished. Check if pass/agree
        local success=0
        local fb_lower
        fb_lower=$(_to_lower "$feedback")
        if [[ "$TYPE" == "loop" ]]; then
          if [[ "$fb_lower" == *"pass"* ]]; then
            success=1
          fi
        elif [[ "$TYPE" == "discuss" ]]; then
          if [[ "$fb_lower" == *"agree"* ]]; then
            success=1
          fi
        fi

        if [[ "$success" == "1" ]]; then
          echo "Reviewer approved the work. Finalizing job as completed."
          "$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" status --job "$JOB_ID" --set "completed"
          final_loop_rc=0
          break
        else
          # Reviewer rejected/provided feedback. Increment & check max iterations
          if [[ $iteration -ge $MAX_ITERATIONS ]]; then
            echo "Max iterations ($MAX_ITERATIONS) reached without approval. Terminating workflow."
            "$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" status --job "$JOB_ID" --set "error"
            final_loop_rc=1
            break
          fi
          
          iteration=$((iteration + 1))
          _phase="worker"
          display_role="$DELEGATE_ROLE"
          current_session="$AGENT_SESSION"
          current_prompt="The reviewer provided the following feedback for job $JOB_ID: $feedback. Please modify the code/artifacts to address these comments. CRITICAL: As the Developer Team Leader, you must thoroughly review the suggested modifications, verify their validity, adopt/implement them if valid, and if you judge any recommendation to be invalid, do NOT implement it but instead explain your reasons clearly in your response and send it back to the reviewer (수정안을 최대한 꼼꼼히 검토하여 타당성을 검증하고, 타당하다면 수렴하여 수정을 진행하되, 타당하지 않다고 판단되는 부분이 있다면 그 이유를 명확히 밝혀 리뷰어에게 전달하십시오)."
        fi
      fi
    done

    # 4) optional validation hook
    if [[ -n "$VALIDATE" ]]; then
      echo "running validation: $VALIDATE"
      if JOB_ID="$JOB_ID" REGISTRY_DIR="$REGISTRY_DIR" bash "$VALIDATE"; then
        echo "validation: PASS"
      else
        local rc=$?
        echo "validation: FAIL (exit $rc)"
        final_loop_rc=$rc
      fi
    fi

    # Last stdout line: the persistent audit-log dir
    local logs_root="${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}"
    echo "$logs_root/$JOB_ID"
    exit "$final_loop_rc"
  fi
}

reap_subscriber() {
  if [ -n "${1:-}" ]; then
    kill "$1" 2>/dev/null || true
    wait "$1" 2>/dev/null || true
  fi
}

_disk_status() {
  local jid="$1"
  "$PY" -c "import json, os; p=os.path.join('$REGISTRY_DIR', '$jid.json'); print(json.load(open(p)).get('status','')) if os.path.exists(p) else print('')" 2>/dev/null || true
}

# ISSUE-14: send_keys_safe can only report what herdr lets it observe. When it
# gives up, the prompt may still have landed - codex under herdr 0.9.0 never
# reports `working`, yet publishes `started` within seconds. Closing such a job
# as agent_blocked strands a live agent whose later `completed` the CAS table
# then refuses. Before finalising, give the registry MAM_INJECT_ACK_GRACE_S
# (default 10 s) to show the agent's own acknowledgement: any status other than
# pending means the prompt was delivered.
_inject_acknowledged() {
  local job_id="$1" deadline st
  deadline=$(( $(date +%s) + ${MAM_INJECT_ACK_GRACE_S:-10} ))
  while :; do
    st="$(_disk_status "$job_id")"
    if [ -n "$st" ] && [ "$st" != "pending" ]; then
      echo "inject: herdr could not confirm delivery, but job $job_id is already '$st' — the agent acknowledged it; continuing." >&2
      return 0
    fi
    [ "$(date +%s)" -ge "$deadline" ] && return 1
    sleep 1
  done
}

# ISSUE-14: herdr's agent_status is blind for some agents (codex stays `idle`
# for the whole turn), so "still working" must also accept the pane's own busy
# token - the same evidence send_keys_safe uses to confirm delivery. Without it
# a codex review longer than the idle timeout was closed as deadline_timeout
# mid-turn and its eventual `completed` refused.
_agent_still_working() {
  local sess="$1" target="$2"
  [ "$(_sks_agent_status "$sess" "$target")" = "working" ] && return 0
  _pane_tail "$sess" 8 2>/dev/null | grep -Fq "esc to interrupt"
}

_map_sub_rc() {
  local sub_rc="$1" job_id="$2"
  local job_status="running"
  if [[ $sub_rc -eq 0 ]]; then
    job_status="completed"
  elif [[ $sub_rc -eq 1 ]]; then
    job_status="error"
  elif [[ $sub_rc -eq 3 ]]; then
    job_status="broker_unavailable"
    local disk_st
    disk_st="$(_disk_status "$job_id")"
    if [[ "$disk_st" == "completed" || "$disk_st" == "error" || "$disk_st" == "cancelled" ]]; then
      job_status="$disk_st"
    fi
  else
    job_status="timeout"
  fi
  echo "$job_status"
}

_rc_for() {
  case "$1" in
    completed) echo 0 ;;
    error) echo 1 ;;
    timeout) echo 2 ;;
    broker_unavailable) echo 3 ;;
    cancelled) echo 5 ;;
    *) echo 1 ;;
  esac
}

finalize_unfinished() {
  local job_id="$1" job_status="$2" sub_rc="$3" phase="${4:-}"
  if [ "${MAM_DELEGATE_FAIL_CLOSED:-1}" = "0" ]; then
    return 0
  fi
  if [ "$job_status" = "completed" ]; then
    return 0
  fi

  local cur_disk_st="$(_disk_status "$job_id")"
  if [ "$cur_disk_st" = "completed" ] || [ "$cur_disk_st" = "error" ] || [ "$cur_disk_st" = "cancelled" ]; then
    FINAL_JOB_STATUS="$cur_disk_st"
    [ "$cur_disk_st" = "completed" ] && return 0
  else
    FINAL_JOB_STATUS="$job_status"
  fi
  # broker_unavailable: re-observe disk for remaining deadline at 3s intervals
  if [ "$job_status" = "broker_unavailable" ]; then
    local now elapsed remaining
    now=$(date +%s)
    elapsed=$(( now - ${START_TIME:-now} ))
    remaining=$(( TIMEOUT - elapsed ))
    [ $remaining -lt 0 ] && remaining=0
    while [ $remaining -gt 0 ]; do
      local wait_interval=3
      [ $remaining -lt $wait_interval ] && wait_interval=$remaining
      sleep "$wait_interval"
      remaining=$(( remaining - wait_interval ))
      local disk_st
      disk_st="$(_disk_status "$job_id")"
      if [ "$disk_st" = "completed" ] || [ "$disk_st" = "error" ] || [ "$disk_st" = "cancelled" ]; then
        job_status="$disk_st"
        FINAL_JOB_STATUS="$disk_st"
        [ "$disk_st" = "completed" ] && return 0
        break
      fi
    done
  fi

  # timeout: if subscriber timed out, check if agent is still actively working in herdr
  if [ "$job_status" = "timeout" ]; then
    local target_sess="${5:-${current_session:-${target_session:-$AGENT_SESSION}}}"
    local sess="${target_sess#herdr:}"
    local target_agent
    target_agent=$(_sanitize_herdr_agent_name "$sess")
    local still_working=0
    if [ "$sess" != "human" ] && [ -n "$sess" ]; then
      _agent_still_working "$sess" "$target_agent" && still_working=1
    fi

    if [ "$still_working" = "1" ]; then
      local now elapsed remaining
      now=$(date +%s)
      elapsed=$(( now - ${START_TIME:-now} ))
      remaining=$(( TIMEOUT - elapsed ))
      [ $remaining -lt 0 ] && remaining=0
      while [ $remaining -gt 0 ]; do
        local disk_st
        disk_st="$(_disk_status "$job_id")"
        if [ "$disk_st" = "completed" ] || [ "$disk_st" = "error" ] || [ "$disk_st" = "cancelled" ]; then
          job_status="$disk_st"
          FINAL_JOB_STATUS="$disk_st"
          [ "$disk_st" = "completed" ] && return 0
          break
        fi
        if ! _agent_still_working "$sess" "$target_agent"; then
          sleep 1
          disk_st="$(_disk_status "$job_id")"
          if [ "$disk_st" = "completed" ] || [ "$disk_st" = "error" ] || [ "$disk_st" = "cancelled" ]; then
            job_status="$disk_st"
            FINAL_JOB_STATUS="$disk_st"
            [ "$disk_st" = "completed" ] && return 0
          fi
          break
        fi
        local wait_interval=1
        [ $remaining -lt $wait_interval ] && wait_interval=$remaining
        sleep "$wait_interval"
        remaining=$(( remaining - wait_interval ))
      done
    fi
  fi

  local close_req="deadline_timeout"
  local detail=""
  if [ "$phase" = "preflight" ] || [ "$phase" = "idle_wait" ]; then
    close_req="cancelled"
    detail="session_busy"
  fi

  local close_args=(--job "$job_id" --request "$close_req")
  [ -n "$detail" ] && close_args+=(--detail "$detail")
  [ -n "${_disp_tok:-}" ] && close_args+=(--expect-dispatch-token "$_disp_tok")

  local close_rc=0
  "$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" close "${close_args[@]}" 2>&1 || close_rc=$?

  # If close failed due to dispatch token mismatch (rc=4), suppress secondary event emission
  # to prevent CAS/ABA bypass
  if [ $close_rc -eq 4 ]; then
    echo "finalize_unfinished: dispatch token mismatch (stale attempt); suppressing event emission" >&2
    return 0
  fi

  # Check if job completed on disk (universal, for all TYPEs)
  local cur_disk_st
  cur_disk_st="$(_disk_status "$job_id")"
  if [ "$cur_disk_st" = "completed" ]; then
    FINAL_JOB_STATUS="completed"
    return 0
  fi

  # direct path only: publish MQTT error/cancelled event (loop/discuss forbidden: O-1)
  if [ "$TYPE" = "direct" ]; then
    local disp_arg=()
    [ -n "${_disp_tok:-}" ] && disp_arg=(--expect-dispatch-token "$_disp_tok")
    if [ "$close_req" = "cancelled" ]; then
      "$PY" "$SCRIPT_DIR/scripts/publish_event.py" --registry-dir "$REGISTRY_DIR" --job "$job_id" --event cancelled --detail "session_busy" ${disp_arg[@]+"${disp_arg[@]}"} 2>/dev/null || true
    elif [ "$sub_rc" -ne 1 ] || [ "$phase" = "inject" ]; then
      local err_detail="deadline_timeout"
      [ "$phase" = "inject" ] && err_detail="agent_blocked"
      "$PY" "$SCRIPT_DIR/scripts/publish_event.py" --registry-dir "$REGISTRY_DIR" --job "$job_id" --event error --detail "$err_detail" ${disp_arg[@]+"${disp_arg[@]}"} 2>/dev/null || true
    fi
  fi
}

preflight_agent() {
  local job_id="$1" target_session="${2:-$AGENT_SESSION}" force_warn_only="${3:-0}"
  if [ "$AGENT" = "human" ]; then
    return 0
  fi
  local sess="${target_session#herdr:}"

  # B-3: must probe for the real binary, not the herdr() function / shim wrapper.
  if ! has_real_herdr; then
    echo "ERROR: this skill requires herdr (interactive agent sessions)." >&2
    echo "       Ensure herdr is installed and executable." >&2
    return 1
  fi

  export HERDR_SESSION_NAME="$(resolve_herdr_session "$sess" "$WORKDIR")"

  if ! herdr has-session -t "$sess" 2>/dev/null; then
    echo "ERROR: 에이전트 세션 '$sess'이 존재하지 않습니다. 작업을 위임하기 전에 먼저 에이전트 세션을 기동해 주세요." >&2
    echo "       팁: 'multi-agent-mux-resume' 또는 'multi-agent-mux-create'를 통해 에이전트를 먼저 생성할 수 있습니다." >&2
    return 1
  fi

  # Check role suitability
  local sess_role job_role
  sess_role=$(SESS_NAME="$sess" MAM_STATE_JSON="$(load_state_json)" "$PY" -c "
import os, json
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
name = os.environ.get('SESS_NAME')
for s in d.get('herdr_sessions', []):
    if s.get('name') == name:
        print(s.get('role', ''))
        break
" 2>/dev/null || echo "")

  job_role=$("$PY" -c "
import json
try:
    with open('$REGISTRY_DIR/$job_id.json') as f:
        print(json.load(f).get('role', ''))
except Exception:
    pass
" 2>/dev/null || echo "")

  if [[ -n "$job_role" && -n "$sess_role" ]]; then
    local check_result
    check_result=$(JOB_ROLE="$job_role" SESS_ROLE="$sess_role" ROLE_ALIASES_JSON="$ROLE_ALIASES_JSON" "$PY" -c "
import os, json
job = os.environ.get('JOB_ROLE', '').lower()
sess = os.environ.get('SESS_ROLE', '').lower()
aliases = json.loads(os.environ.get('ROLE_ALIASES_JSON', '{}'))
candidates = aliases.get(job, [job])
if any(c in sess for c in candidates):
    print('OK')
else:
    print('MISMATCH')
" 2>/dev/null || echo "OK")

    if [[ "$check_result" == "MISMATCH" ]]; then
      local mismatch_msg="Target session '$sess' has role '$sess_role' which does not match job role '$job_role'."
      if [[ "$STRICT_ROLE_CHECK" -eq 1 && "$force_warn_only" -ne 1 ]]; then
        echo "ERROR: role suitability mismatch. $mismatch_msg" >&2
        return 1
      else
        echo "WARNING: $mismatch_msg" >&2
      fi
    fi
  fi
  return 0
}

inject_agent() {
  local job_id="$1" instructions="$2" target_session="${3:-$AGENT_SESSION}"
  if [ "$AGENT" = "human" ]; then
    echo "[human agent] complete the task, then run publish_event.py --event completed"
    return 0
  fi
  local sess="${target_session#herdr:}"

  # Before launching the agent, set up error trap to publish error event
  if [ -n "${job_id:-}" ] && [ -n "${PY:-}" ]; then
    pub_script="$SCRIPT_DIR/scripts/publish_event.py"
    trap "rc=\$?; if [ \$rc -ne 0 ]; then \"$PY\" \"$pub_script\" --registry-dir '$REGISTRY_DIR' --job '$job_id' --event error --detail 'agent bootstrap failed (exit '\$rc')'; fi" EXIT
  fi

  echo "살아있는 에이전트 세션 '$sess'에 작업을 위임합니다..."
  if ! send_keys_safe "$sess" "$instructions" "$job_id" && ! _inject_acknowledged "$job_id"; then
    trap - EXIT
    echo "ERROR: 프롬프트 주입 실패 — 세션 '$sess' (프롬프트 잠금 의심)" >&2
    return 1
  fi

  echo "작업이 세션 '$sess'에 전송되었습니다. (연결하려면: HERDR_SESSION_NAME=$HERDR_SESSION_NAME herdr agent attach $sess — lib.sh를 source한 셸에서 실행)"
  trap - EXIT
  return 0
}

run_agent() {
  local job_id="$1" instructions="$2" target_session="${3:-$AGENT_SESSION}" force_warn_only="${4:-0}"
  local sess="${target_session#herdr:}"
  if [[ "$DRY_RUN" == "1" ]]; then
    echo "[dry-run] would delegate task to running agent '$AGENT' in herdr session '$sess' with instructions:"
    echo "----"; echo "$instructions"; echo "----"
    return 0
  fi
  preflight_agent "$job_id" "$target_session" "$force_warn_only" || return $?
  inject_agent "$job_id" "$instructions" "$target_session" || return $?
}

cmd_status() {
  parse_opts "$@"
  [[ -n "$JOB_ID" ]] || { echo "status requires --job" >&2; exit 1; }
  PY="$(pick_python)"
  "$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" get --job "$JOB_ID"
}

cmd_list() {
  parse_opts "$@"
  PY="$(pick_python)"
  "$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" list
}

cmd_verify() {
  parse_opts "$@"
  [[ -n "$JOB_ID" ]] || { echo "verify requires --job" >&2; exit 1; }
  [[ -n "$VALIDATE" ]] || { echo "verify requires --validate <script>" >&2; exit 1; }
  echo "verifying job $JOB_ID with $VALIDATE"
  if JOB_ID="$JOB_ID" REGISTRY_DIR="$REGISTRY_DIR" bash "$VALIDATE"; then
    echo "verify: PASS (exit 0)"; exit 0
  else
    rc=$?; echo "verify: FAIL (exit $rc)"; exit "$rc"
  fi
}

cmd_logs() {
  # logs <job_id> | logs --list — delegates to registry.py's logs CLI, which
  # reads the persistent audit log under $DELEGATE_JOB_LOGS_DIR (or
  # <cwd>/delegate_job_logs). Run from your project dir so the default resolves.
  PY="$(pick_python)"
  if [[ "${1:-}" == "--list" ]]; then
    "$PY" "$SCRIPT_DIR/scripts/registry.py" logs --list
  else
    local jid="${1:-}"
    [[ -n "$jid" ]] || { echo "logs requires <job_id> or --list" >&2; exit 1; }
    "$PY" "$SCRIPT_DIR/scripts/registry.py" logs "$jid"
  fi
}

cmd_wait() {
  parse_opts "$@"
  PY="$(pick_python)"
  if [[ -n "$JOB_ID" ]]; then
    "$PY" "$SCRIPT_DIR/scripts/job_subscriber.py" --registry-dir "$REGISTRY_DIR" \
        --job "$JOB_ID" --timeout "$TIMEOUT"
  else
    "$PY" "$SCRIPT_DIR/scripts/job_subscriber.py" --registry-dir "$REGISTRY_DIR" \
        --wait-any --timeout "$TIMEOUT"
  fi
}

main() {
  local sub="${1:-}"; shift || true
  case "$sub" in
    submit) cmd_submit "$@";;
    status) cmd_status "$@";;
    list)   cmd_list "$@";;
    verify) cmd_verify "$@";;
    wait)   cmd_wait "$@";;
    logs)   cmd_logs "$@";;
    ""|-h|--help|help) usage;;
    *) echo "unknown command: $sub" >&2; usage; exit 1;;
  esac
}

main "$@"
