#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"

### new.method

# ============================================================================
# hiveMind - Multi-agent orchestrator for oosh
# Manages REAL Claude Code agents in tmux panes using agentRoom backend
# ============================================================================

# ─────────────────────────────────────────────────────────────────────────────
# CONFIGURATION
# ─────────────────────────────────────────────────────────────────────────────

: ${HIVEMIND_SESSION:=hivemind}
: ${HIVEMIND_MAIN_PANE_SIZE:=60}
: ${HIVEMIND_BASE_PORT:=11080}

# ── Naming conventions ────────────────────────────────────────────────────────
# tmux session  : <teamName>            e.g. cursorOrchestrator, ooshTeam
# tmux window   : team                  window 0 is always "team"
# env per pane  : HIVEMIND_ROLE=<role>  exported before Claude Code starts
# role registry : ~/config/hivemind.roles.env  maps pane targets → role names
#                 format: target|role  (persistent, survives reboot)
: ${HIVEMIND_WINDOW_NAME:=team}
: ${HIVEMIND_REGISTRY:=${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}
: ${HIVEMIND_SESSIONS:=${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}

# Default agent roles
HIVEMIND_DEFAULT_AGENTS=(
  "orchestrator"
  "coder"
  "tester"
  "reviewer"
)

# OOSH specialized agent roles
HIVEMIND_OOSH_AGENTS=(
  "oosh-expert"
  "oosh-tester"
)

# Canonical location for agent role definitions
# Derives from OOSH_DIR (set by 'source this') → workspace root is 3 levels up
HIVEMIND_AGENTS_DIR="${HIVEMIND_AGENTS_DIR:-${OOSH_DIR:+${OOSH_DIR}/../../../.claude/agents}}"

# Agent role descriptions (used for system prompts)
# Bash 3.2 compatible — uses function lookup instead of declare -A
# Roles with SKILL.md files get auto-loaded from .claude/agents/<role>/SKILL.md
private.hiveMind.get.role.prompt() {
  case "$1" in
    orchestrator|oosh-orchestrator|agent-teacher) echo "You are the Agent Teacher. Read .claude/agents/agent-teacher/SKILL.md to learn your role. Train agents, delegate tasks, improve tools, and maintain context in session/agent.context.md." ;;
    coder)               echo "You implement features and write code." ;;
    tester)              echo "You write and run tests." ;;
    reviewer)            echo "You review code for quality and best practices." ;;
    oosh-expert)         echo "You are an OOSH framework expert. Read .claude/agents/oosh-expert/SKILL.md to learn your role. Specialize in architecture, method patterns, completion system (c2), and framework development." ;;
    oosh-tester)         echo "You are an OOSH testing specialist. Read .claude/agents/oosh-tester/SKILL.md to learn your role. Specialize in test.suite, writing tests, and validating oosh scripts." ;;
    scrum-master)        echo "You are the ScrumMaster. Read .claude/agents/scrum-master/SKILL.md to learn your role. Monitor agent panes, approve permissions, enforce role boundaries, and report issues." ;;
    product-owner)       echo "You are the Product Owner. Read .claude/agents/product-owner/SKILL.md to learn your role. Uphold first principles, govern script ownership by expert+tester pairs, enforce the usability contract." ;;
    script-product-owner) echo "Read .claude/agents/script-product-owner/SKILL.md — this is the script ownership contract, not a separate agent role. Expert+tester pairs own scripts; the Product Owner governs first principles." ;;
    developer)           echo "You are a Developer agent. Read .claude/agents/developer/SKILL.md to learn your role. Implement assigned work following OOSH patterns." ;;
    agent-trainer)       echo "You are the Agent Trainer. Read .claude/agents/agent-trainer/SKILL.md to learn your role. Improve all agent SKILL.md files based on team learnings. You do NOT implement features or run tests." ;;
    task-agent)          echo "You are a Task Agent. Read .claude/agents/task-agent/SKILL.md to learn your role. Execute assigned tasks autonomously within your designated scope." ;;
    woda-writer)         echo "You are the WODA Writer. Read .claude/agents/woda-writer/SKILL.md to learn your role. Write chapters, maintain learnings, and monitor your scribe peer." ;;
    woda-scribe)         echo "You are the WODA Scribe. Read .claude/agents/woda-scribe/SKILL.md to learn your role. Monitor the writer, maintain the knowledge base, implement improvements, and track context health." ;;
    *)                   return 1 ;;
  esac
}

# ─────────────────────────────────────────────────────────────────────────────
# PRIVATE HELPERS
# ─────────────────────────────────────────────────────────────────────────────

private.hiveMind.pane.identify() {
  # Register role for a pane: write to registry, set pane title, export env var
  # Usage: private.hiveMind.pane.identify <target> <role>
  local target="$1"
  local role="$2"
  private.hiveMind.registry.set "$target" "$role"
  otmux pane.title "$target" "$role"
  otmux send.enter "$target" "export HIVEMIND_ROLE=$role"
}

# ── Role Registry ─────────────────────────────────────────────────────────────
# File-based mapping: pane target → role name.
# Survives Claude Code overwriting pane titles.
# Format: target|role  (one per line, e.g. cursorOrchestrator:0.2|oosh-expert)
# Location: ~/config/hivemind.roles.env (persistent, OOSH config pattern)

private.hiveMind.registry.migrate() {
  # Migrate old /tmp/ registry to config pattern (one-time, backward compat)
  local old_registry="/tmp/hivemind.roles"
  [ -f "$old_registry" ] || return 0
  [ "$HIVEMIND_REGISTRY" = "$old_registry" ] && return 0
  if [ ! -f "$HIVEMIND_REGISTRY" ] || [ ! -s "$HIVEMIND_REGISTRY" ]; then
    local config_dir
    config_dir=$(dirname "$HIVEMIND_REGISTRY")
    mkdir -p "$config_dir" 2>/dev/null
    cp "$old_registry" "$HIVEMIND_REGISTRY"
    info.log "Migrated registry from $old_registry to $HIVEMIND_REGISTRY"
  fi
}

private.hiveMind.registry.set() {
  # Add or update a target→role mapping
  local target="$1"
  local role="$2"
  [ -z "$target" ] || [ -z "$role" ] && return 1
  private.hiveMind.registry.migrate
  # Ensure config directory exists
  local config_dir
  config_dir=$(dirname "$HIVEMIND_REGISTRY")
  mkdir -p "$config_dir" 2>/dev/null
  # Remove old entry for this target, then append new one
  if [ -f "$HIVEMIND_REGISTRY" ]; then
    grep -v "^${target}|" "$HIVEMIND_REGISTRY" > "${HIVEMIND_REGISTRY}.tmp" 2>/dev/null
    mv "${HIVEMIND_REGISTRY}.tmp" "$HIVEMIND_REGISTRY"
  fi
  echo "${target}|${role}" >> "$HIVEMIND_REGISTRY"
}

private.hiveMind.registry.get() {
  # Look up role for a given pane target
  local target="$1"
  private.hiveMind.registry.migrate
  [ -f "$HIVEMIND_REGISTRY" ] || return 1
  grep "^${target}|" "$HIVEMIND_REGISTRY" 2>/dev/null | head -1 | cut -d'|' -f2
}

private.hiveMind.registry.find() {
  # Find pane target by role name (case-insensitive partial match)
  local name="$1"
  local session="$2"
  private.hiveMind.registry.migrate
  [ -f "$HIVEMIND_REGISTRY" ] || return 1
  if [ -n "$session" ]; then
    grep "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null | grep -i "$name" | head -1 | cut -d'|' -f1
  else
    grep -i "$name" "$HIVEMIND_REGISTRY" 2>/dev/null | head -1 | cut -d'|' -f1
  fi
}

private.hiveMind.registry.list() {
  # List all entries, optionally filtered by session
  local session="$1"
  private.hiveMind.registry.migrate
  [ -f "$HIVEMIND_REGISTRY" ] || return 0
  if [ -n "$session" ]; then
    grep "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null
  else
    cat "$HIVEMIND_REGISTRY" 2>/dev/null
  fi
}

# ── Session Tracking ──────────────────────────────────────────────────────────
# File-based mapping: role name → Claude session UUID.
# Allows rejoining a session after the Claude process exits.
# Format: role|session-uuid  (one per line)

private.hiveMind.session.store() {
  # Store or update role → session UUID mapping
  local role="$1"
  local session_id="$2"
  [ -z "$role" ] || [ -z "$session_id" ] && return 1
  # Remove old entry for this role, then append new one
  if [ -f "$HIVEMIND_SESSIONS" ]; then
    grep -v "^${role}|" "$HIVEMIND_SESSIONS" > "${HIVEMIND_SESSIONS}.tmp" 2>/dev/null
    mv "${HIVEMIND_SESSIONS}.tmp" "$HIVEMIND_SESSIONS"
  fi
  echo "${role}|${session_id}" >> "$HIVEMIND_SESSIONS"
}

private.hiveMind.session.lookup() {
  # Look up stored session UUID for a given role
  local role="$1"
  [ -f "$HIVEMIND_SESSIONS" ] || return 1
  grep "^${role}|" "$HIVEMIND_SESSIONS" 2>/dev/null | tail -1 | cut -d'|' -f2
}

private.hiveMind.resolve.alias() {
  # Map legacy/alternate names to canonical registry keys.
  # The registry stores canonical names; this resolves aliases when direct lookup fails.
  case "$1" in
    agent-teacher|oosh-orchestrator) echo "orchestrator" ;;
    *) echo "$1" ;;
  esac
}

# Session ID detection delegated to claudeCode wrapper (DRY).
# Use: claudeCode process.find <pane>    — get Claude PID
#      claudeCode process.running <pane> — boolean check
#      claudeCode session.id <pane>      — get session UUID

private.hiveMind.pane.activity() {
  # Detect real Claude Code activity state from pane content.
  # Returns one of: active | idle | permission | unknown
  local target="$1"
  [ -z "$target" ] && echo "unknown" && return 1

  local content
  content=$(tmux capture-pane -t "$target" -p -S -5 2>/dev/null)

  # Empty or whitespace-only → unknown
  local trimmed
  trimmed=$(echo "$content" | tr -d '[:space:]')
  [ -z "$trimmed" ] && echo "unknown" && return 0

  # Permission prompt: Claude Code shows "Allow" and "Deny" options together
  if echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
    echo "permission"
    return 0
  fi

  # Idle: last non-empty line is the input prompt character
  local last_line
  last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)
  if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]*$'; then
    echo "idle"
    return 0
  fi

  # Default: active (generating text, running tools, or indeterminate)
  echo "active"
  return 0
}

private.hiveMind.get.agents() {
  # Get all agent IDs from agentRoom
  agentRoom list.ids 2>/dev/null
}

private.hiveMind.get.agent.port() {
  local agent_id="$1"
  agentRoom list 2>/dev/null | grep "^$agent_id " | awk '{print $2}'
}

private.hiveMind.pane.for.agent() {
  local agent_id="$1"
  tmux list-panes -a -F "#{pane_id} #{pane_title}" 2>/dev/null | grep "$agent_id" | awk '{print $1}' | head -1
}

private.hiveMind.create.agent.pane() {
  local agent_id="$1"
  local port="$2"
  local workdir="${3:-$(pwd)}"
  local window="${4:-agents}"

  # Select or create agents window
  if ! tmux list-windows -F "#{window_name}" 2>/dev/null | grep -q "^${window}$"; then
    tmux new-window -n "$window"
  else
    tmux select-window -t "$window"
  fi

  # Create new pane
  tmux split-window -h
  tmux select-layout tiled

  # Get the new pane
  local pane_id=$(tmux display-message -p "#{pane_id}")

  # Set pane title and export role env var
  private.hiveMind.pane.identify "$pane_id" "$agent_id"

  # Start Claude Code in the pane with agent role
  otmux send "$pane_id" "cd '$workdir' && claude" Enter

  echo "$pane_id"
}

# ─────────────────────────────────────────────────────────────────────────────
# PARAMETER COMPLETIONS (shared across all methods)
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.parameter.completion.agentId() {
  hiveMind.list
}

# ─────────────────────────────────────────────────────────────────────────────
# INITIALIZATION
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.init() # <?agents:orchestrator,coder,tester,reviewer> <?workdir:.> # initialize hivemind with real Claude agents
{
  local agents="${1:-orchestrator,coder,tester,reviewer}"
  local workdir="${2:-$(pwd)}"

  info.log "Initializing HiveMind with agents: $agents"

  # Ensure agentRoom is available
  if ! command -v agentRoom &>/dev/null && [ ! -f "$OOSH_DIR/agentRoom" ]; then
    error.log "agentRoom script not found. Ensure OOSH is properly set up."
    return 1
  fi

  # Start agentRoom backend
  info.log "Starting agentRoom backend..."
  agentRoom backend.start "$HIVEMIND_BASE_PORT"

  sleep 2

  # Check if in tmux or create session
  if [ -z "$TMUX" ]; then
    info.log "Creating tmux session: $HIVEMIND_SESSION"
    tmux new-session -d -s "$HIVEMIND_SESSION" -n "main"

    # Main pane for orchestrator
    private.hiveMind.pane.identify "${HIVEMIND_SESSION}:0.0" "orchestrator"
  fi

  # Start each agent
  IFS=',' read -ra agent_array <<< "$agents"
  local port=$((HIVEMIND_BASE_PORT + 1))

  for agent_id in "${agent_array[@]}"; do
    info.log "Starting agent: $agent_id on port $port"

    # Start agentRoom agent backend
    agentRoom agent.start "$agent_id" "$port" "$workdir" "$agent_id"

    # Create tmux pane for agent
    hiveMind.pane.create "$agent_id" "$workdir"

    ((port++))
    sleep 1
  done

  success.log "HiveMind initialized!"
  echo ""
  echo "Agents running:"
  agentRoom list
  echo ""
  echo "Tmux session: $HIVEMIND_SESSION"
  [ -z "$TMUX" ] && echo "Attach with: tmux attach -t $HIVEMIND_SESSION"
}

hiveMind.init.completion.agents() {
  echo "orchestrator,coder,tester,reviewer"
  echo "coder,tester"
  echo "orchestrator,coder,tester,reviewer,researcher"
  echo "oosh-expert,oosh-tester"
}

hiveMind.pane.create() # <agentId> <?workdir:.> # create tmux pane with Claude Code for agent
{
  local agent_id="$1"
  local workdir="${2:-$(pwd)}"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind pane.create <agentId> <?workdir>"
    return 1
  fi

  # Check if pane already exists
  local existing_pane=$(private.hiveMind.pane.for.agent "$agent_id")
  if [ -n "$existing_pane" ]; then
    info.log "Pane for $agent_id already exists: $existing_pane"
    return 0
  fi

  info.log "Creating pane for $agent_id..."

  # Get agent port
  local port=$(private.hiveMind.get.agent.port "$agent_id")

  # Create pane in agents window
  if [ -n "$TMUX" ]; then
    # Check if agents window exists
    if ! tmux list-windows -F "#{window_name}" | grep -q "^agents$"; then
      tmux new-window -n "agents"
    else
      tmux select-window -t agents
    fi

    # Split and create new pane
    tmux split-window -h -t agents
    tmux select-layout -t agents tiled

    # Set title and export role env var
    local pane_id=$(tmux display-message -p "#{pane_id}")
    private.hiveMind.pane.identify "$pane_id" "$agent_id"

    # Get system prompt for role (use role-specific or generic)
    local system_prompt
    system_prompt=$(private.hiveMind.get.role.prompt "$agent_id") || system_prompt="You are the $agent_id agent. Focus on your specialized role."

    # Start Claude Code with role context
    otmux send "$pane_id" "cd '$workdir'" Enter
    otmux send "$pane_id" "echo '=== $agent_id Agent (port: $port) ==='" Enter
    otmux send "$pane_id" "claude -p '$system_prompt'" Enter

    success.log "Created pane for $agent_id"
  else
    warn.log "Not in tmux session. Run: hiveMind attach first"
    return 1
  fi
}

# ─────────────────────────────────────────────────────────────────────────────
# SESSION MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.attach() # # attach to hivemind tmux session
{
  if tmux has-session -t "$HIVEMIND_SESSION" 2>/dev/null; then
    tmux attach -t "$HIVEMIND_SESSION"
  else
    warn.log "HiveMind session not found. Run: hiveMind init"
    return 1
  fi
}

hiveMind.detach() # # detach from current session
{
  tmux detach-client
}

hiveMind.join() # <name> # rejoin an agent's Claude session by role name
{
  local name="$1"

  if [ -z "$name" ]; then
    error.log "Usage: hiveMind join <name>"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  # Resolve agent name to pane target
  local target
  target=$(hiveMind.resolve "$name")
  if [ $? -ne 0 ]; then
    return 1
  fi

  # Try to find session ID from a running Claude process first
  local session_id
  session_id=$(claudeCode session.id "$target" 2>/dev/null)

  if [ -n "$session_id" ]; then
    # Claude is still running — store the session ID and inform user
    private.hiveMind.session.store "$name" "$session_id"
    info.log "Claude is already running in $target (session: ${session_id:0:8}...)"
    echo "Session $session_id is active in pane $target"
    return 0
  fi

  # Process not running — look up stored session ID
  session_id=$(private.hiveMind.session.lookup "$name")

  if [ -z "$session_id" ]; then
    error.log "No session found for agent '$name'"
    echo "  The agent may not have been started yet, or session data was lost."
    echo "  Start fresh with: hiveMind agent.bootstrap $name"
    return 1
  fi

  info.log "Resuming session ${session_id:0:8}... for $name in pane $target"

  # Send resume command to the pane
  otmux send "$target" "claude --resume $session_id" Enter

  success.log "Sent resume command for $name (session: ${session_id:0:8}...)"
  return 0
}

hiveMind.join.completion.name() {
  hiveMind.resolve.completion.name
}

hiveMind.kill() # # shutdown hivemind completely
{
  info.log "Shutting down HiveMind..."

  # Stop all agentRoom agents
  agentRoom stop.all 2>/dev/null

  # Kill tmux session
  if tmux has-session -t "$HIVEMIND_SESSION" 2>/dev/null; then
    tmux kill-session -t "$HIVEMIND_SESSION"
    success.log "HiveMind session killed"
  else
    info.log "No HiveMind tmux session found"
  fi
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.spawn() # <agentId> <?workdir:.> # spawn a new agent with pane
{
  local agent_id="$1"
  local workdir="${2:-$(pwd)}"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind spawn <agentId> <?workdir>"
    return 1
  fi

  # Find next free port
  local used_ports=$(agentRoom list.ports 2>/dev/null | sort -n | tail -1)
  local port=$((used_ports + 1))
  [ -z "$used_ports" ] && port=$((HIVEMIND_BASE_PORT + 1))

  info.log "Spawning agent $agent_id on port $port..."

  # Start agent backend
  agentRoom agent.start "$agent_id" "$port" "$workdir" "$agent_id"

  # Create pane
  hiveMind.pane.create "$agent_id" "$workdir"

  success.log "Agent $agent_id spawned"
}

hiveMind.spawn.completion.type() {
  echo "orchestrator"
  echo "coder"
  echo "tester"
  echo "reviewer"
  echo "oosh-expert"
  echo "oosh-tester"
  echo "scrum-master"
  echo "developer"
  echo "agent-trainer"
  echo "task-agent"
}

hiveMind.list() # # list all agents
{
  if command -v agentRoom &>/dev/null && agentRoom backend.status &>/dev/null 2>&1; then
    private.hiveMind.get.agents
  else
    # Fall back to tmux pane titles when agentRoom is unavailable
    local session="${HIVEMIND_SESSION:-cursorOrchestrator}"
    if tmux has-session -t "$session" 2>/dev/null; then
      tmux list-panes -t "$session" -F "#{pane_title}" 2>/dev/null
    else
      echo "queen"
    fi
  fi
}

hiveMind.workers() # # list worker agents (non-orchestrator panes)
{
  if command -v agentRoom &>/dev/null && agentRoom backend.status &>/dev/null 2>&1; then
    agentRoom list.ids 2>/dev/null | grep -v "queen"
  else
    local session="${HIVEMIND_SESSION:-cursorOrchestrator}"
    tmux list-panes -t "$session" -F "#{pane_title}" 2>/dev/null | grep -v -E "^(orchestrator|queen)$"
  fi
}

hiveMind.queen() # # show queen (orchestrator) agent ID
{
  if command -v agentRoom &>/dev/null && agentRoom backend.status &>/dev/null 2>&1; then
    agentRoom list.ids 2>/dev/null | grep "queen" | head -1
  else
    echo "queen"
  fi
}

hiveMind.status() # <?session> # show one-line hivemind overview
{
  local session="${1:-cursorOrchestrator}"

  if tmux has-session -t "$session" 2>/dev/null; then
    local pane_count
    pane_count=$(tmux list-panes -t "$session" -s 2>/dev/null | wc -l | tr -d ' ')
    local role_count=0
    if [ -f "$HIVEMIND_REGISTRY" ]; then
      role_count=$(grep -c "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null || echo 0)
    fi
    echo "$session: $pane_count panes, $role_count registered agents"
  else
    echo "$session: not running"
  fi
}
hiveMind.status.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.focus() # <agentId> # focus on specific agent pane
{
  local agent_id="$1"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind focus <agentId>"
    return 1
  fi

  local pane=$(private.hiveMind.pane.for.agent "$agent_id")

  if [ -n "$pane" ]; then
    tmux select-pane -t "$pane"
    success.log "Focused on $agent_id"
  else
    error.log "No pane found for agent: $agent_id"
    return 1
  fi
}

hiveMind.focus.completion.agentId() {
  hiveMind.list
}

hiveMind.resolve() # <name> <?session> # resolve agent name to pane target via role registry
{
  local name="$1"
  local session="${2:-cursorOrchestrator}"

  if [ -z "$name" ]; then
    error.log "Usage: hiveMind resolve <name>"
    return 1
  fi

  local match
  match=$(private.hiveMind.registry.find "$name" "$session")

  # If no direct match, try canonical alias (e.g. agent-teacher → orchestrator)
  if [ -z "$match" ]; then
    local canonical
    canonical=$(private.hiveMind.resolve.alias "$name")
    if [ "$canonical" != "$name" ]; then
      match=$(private.hiveMind.registry.find "$canonical" "$session")
    fi
  fi

  if [ -n "$match" ]; then
    echo "$match"
    return 0
  else
    error.log "No agent matching '$name' in registry (session: $session)"
    return 1
  fi
}

hiveMind.resolve.completion.name() {
  local session="cursorOrchestrator"
  [ -f "$HIVEMIND_REGISTRY" ] && grep "^${session}:" "$HIVEMIND_REGISTRY" 2>/dev/null | cut -d'|' -f2
}

hiveMind.send() # <name> <text...> # send literal text to agent by name (no Enter appended)
{
  local name="$1"
  shift

  if [ -z "$name" ] || [ -z "$*" ]; then
    error.log "Usage: hiveMind send <name> <text...>"
    return 1
  fi

  local target
  target=$(hiveMind.resolve "$name")
  if [ $? -ne 0 ]; then
    return 1
  fi

  # Use -l flag to send literal text, preserving spaces
  # Without -l, tmux interprets words as key names and strips spaces
  otmux send "$target" -l "$*"
  info.log "Sent to $name ($target): $*"
  return 0
}
hiveMind.send.completion.name() {
  hiveMind.resolve.completion.name
}

hiveMind.send.enter() # <name> <message> # send message to agent by name, followed by Enter
{
  local name="$1"
  shift
  local message="$*"

  if [ -z "$name" ] || [ -z "$message" ]; then
    error.log "Usage: hiveMind send.enter <name> <message>"
    return 1
  fi

  local target
  target=$(hiveMind.resolve "$name")
  if [ $? -ne 0 ]; then
    return 1
  fi

  otmux send.enter "$target" "$message"
  info.log "Sent to $name ($target): $message"
  return 0
}
hiveMind.send.enter.completion.name() {
  hiveMind.resolve.completion.name
}

# ─────────────────────────────────────────────────────────────────────────────
# TRANSPORT-INDEPENDENT MESSAGING
# ─────────────────────────────────────────────────────────────────────────────

private.hiveMind.channel.resolve() # <name> # resolve agent name to best available communication channel
{
  local name="$1"
  [ -z "$name" ] && return 1

  # Channel 1: tmux pane via registry
  local pane_target
  pane_target=$(hiveMind.resolve "$name" 2>/dev/null)
  if [ -n "$pane_target" ]; then
    echo "pane|${pane_target}"
    return 0
  fi

  # Channel 2: agentRoom API (if backend is running)
  if command -v agentRoom >/dev/null 2>&1; then
    local status
    status=$(agentRoom backend.status 2>/dev/null)
    if ! echo "$status" | grep -q "not running"; then
      echo "api|${name}"
      return 0
    fi
  fi

  # No channel available
  return 1
}

hiveMind.agent.send() # <name> <message> # send message to agent via best available channel
{
  local name="$1"
  shift
  local message="$*"

  if [ -z "$name" ] || [ -z "$message" ]; then
    error.log "Usage: hiveMind agent.send <name> <message>"
    return 1
  fi

  local channel
  channel=$(private.hiveMind.channel.resolve "$name")
  if [ $? -ne 0 ]; then
    error.log "No communication channel available for agent '$name'"
    return 1
  fi

  local transport="${channel%%|*}"
  local target="${channel#*|}"

  case "$transport" in
    pane)
      otmux send.enter "$target" "$message"
      ;;
    api)
      agentRoom chat "$target" "$message"
      ;;
    *)
      error.log "Unknown transport: $transport"
      return 1
      ;;
  esac

  info.log "Sent to $name via $transport ($target): $message"
  return 0
}
hiveMind.agent.send.completion.name() {
  hiveMind.resolve.completion.name
}

# ─────────────────────────────────────────────────────────────────────────────
# TASK MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.task() # <agentId> <description> # send task to specific agent
{
  local agent_id="$1"
  shift
  local description="$*"

  if [ -z "$agent_id" ] || [ -z "$description" ]; then
    error.log "Usage: hiveMind task <agentId> <description>"
    return 1
  fi

  info.log "Sending task to $agent_id: $description"

  # Send via tmux pane
  local pane=$(private.hiveMind.pane.for.agent "$agent_id")
  if [ -n "$pane" ]; then
    otmux send.enter "$pane" "$description"
  else
    # Fallback to API
    agentRoom chat "$agent_id" "$description"
  fi
}

hiveMind.broadcast() # <message> # send message to all agents
{
  local message="$1"

  if [ -z "$message" ]; then
    error.log "Usage: hiveMind broadcast <message>"
    return 1
  fi

  info.log "Broadcasting: $message"

  for agent_id in $(hiveMind.list); do
    hiveMind.send.enter "$agent_id" "$message"
  done
}

# ─────────────────────────────────────────────────────────────────────────────
# DISCOVERY
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.discover() # # discover all agents as JSON
{
  agentRoom discover
}

hiveMind.panes() # # list all agent panes
{
  echo "HiveMind Agent Panes:"
  echo "─────────────────────────────────────────"
  tmux list-panes -a -F "#{pane_id} #{pane_title} #{pane_current_command}" 2>/dev/null | \
    while read pane_id title cmd; do
      # Filter to only show agent panes
      if echo "$title" | grep -qE "orchestrator|coder|tester|reviewer|researcher|oosh-expert|oosh-tester|scrum-master|product-owner|developer|agent-trainer|task-agent"; then
        printf "%-12s %-20s %s\n" "$pane_id" "$title" "$cmd"
      fi
    done
  echo "─────────────────────────────────────────"
}

# ─────────────────────────────────────────────────────────────────────────────
# CLAUDE CODE INTEGRATION
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.claude() # <agentId> <?prompt> # interact with agent via Claude Code
{
  local agent_id="$1"
  shift
  local prompt="$*"

  if [ -z "$agent_id" ]; then
    error.log "Usage: hiveMind claude <agentId> <?prompt>"
    return 1
  fi

  local pane=$(private.hiveMind.pane.for.agent "$agent_id")

  if [ -z "$pane" ]; then
    error.log "No pane found for agent: $agent_id"
    return 1
  fi

  if [ -z "$prompt" ]; then
    # Focus on agent pane
    tmux select-pane -t "$pane"
  else
    # Send prompt to agent
    otmux send "$pane" "$prompt" Enter
  fi
}

# ─────────────────────────────────────────────────────────────────────────────
# OOSH SPECIALIST AGENTS
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.oosh.init() # <?session:cursorOrchestrator> # initialize OOSH expert and tester agents in existing session
{
  local session="${1:-cursorOrchestrator}"
  
  info.log "Initializing OOSH specialist agents in session: $session"
  
  # Check if session exists
  if ! tmux has-session -t "$session" 2>/dev/null; then
    error.log "Session $session not found. Create it first with otmux new $session"
    return 1
  fi
  
  # List existing panes
  local pane_count=$(tmux list-panes -t "$session" -F "#{pane_id}" | wc -l | tr -d ' ')
  info.log "Session has $pane_count panes"
  
  success.log "OOSH agents ready in session: $session"
  echo ""
  echo "Use these methods to teach and interact:"
  echo "  hiveMind.teach <pane> <role>    # Teach agent its role"
  echo "  hiveMind.send.enter <name> <msg> # Send message to agent (with Enter)"
  echo "  hiveMind.send <name> <keys>     # Send raw keys to agent (no Enter)"
  echo "  hiveMind.task <agentId> <task>  # Assign task to agent"
}

hiveMind.team.setup.oosh() # <?session:cursorOrchestrator> # create 3-pane tmux session with oosh-orchestrator, expert, and tester agents
{
  local session="${1:-cursorOrchestrator}"

  info.log "Setting up OOSH team in session: $session"

  # Check if session already exists
  if tmux has-session -t "$session" 2>/dev/null; then
    warn.log "Session '$session' already exists"
    echo "  Kill it first:  tmux kill-session -t $session"
    echo "  Or attach to it: tmux attach -t $session"
    return 1
  fi

  # ── Create session and pane layout ──────────────────────────────────────
  # ┌─────────────────────────────────────────┐
  # │ Pane 0 - ORCHESTRATOR (oosh-orchestrator)│
  # ├───────────────────────┬─────────────────┤
  # │ Pane 1 - EXPERT       │ Pane 2 - TESTER │
  # │ (oosh-expert)         │ (oosh-tester)   │
  # └───────────────────────┴─────────────────┘

  info.log "Creating tmux session: $session"
  otmux new "$session" -d

  # Name window 0
  tmux rename-window -t "${session}:0" "$HIVEMIND_WINDOW_NAME"

  # Split pane 0 vertically → pane 0 (upper) + pane 1 (lower)
  otmux split.v -t "${session}:0.0"

  # Split pane 1 horizontally → pane 1 (lower-left) + pane 2 (lower-right)
  otmux split.h -t "${session}:0.1"

  # Enable pane border labels (agent names on top border, bold if active)
  tmux set -g pane-border-status top
  tmux set -g pane-border-format " #{?pane_active,#[bold],}#T#[default] "

  # Set pane titles and export role env vars
  private.hiveMind.pane.identify "${session}:0.0" "oosh-orchestrator"
  private.hiveMind.pane.identify "${session}:0.1" "oosh-expert"
  private.hiveMind.pane.identify "${session}:0.2" "oosh-tester"

  # ── Start Claude Code in each pane ─────────────────────────────────────
  info.log "Starting Claude Code in all panes..."

  otmux send.enter "${session}:0.0" "claude"
  sleep 2
  otmux send.enter "${session}:0.1" "claude"
  sleep 2
  otmux send.enter "${session}:0.2" "claude"

  # Wait for Claude Code to initialize
  info.log "Waiting for Claude Code agents to initialize..."
  sleep 8

  # ── Send role prompts ──────────────────────────────────────────────────
  info.log "Sending role prompts to agents..."

  otmux send.enter "${session}:0.0" "$(private.hiveMind.get.role.prompt oosh-orchestrator)"
  sleep 1
  otmux send.enter "${session}:0.1" "$(private.hiveMind.get.role.prompt oosh-expert)"
  sleep 1
  otmux send.enter "${session}:0.2" "$(private.hiveMind.get.role.prompt oosh-tester)"

  # ── Done ───────────────────────────────────────────────────────────────
  success.log "OOSH team created in session: $session"
  echo ""
  echo "Pane layout:"
  echo "┌─────────────────────────────────────────┐"
  echo "│ Pane 0 - ORCHESTRATOR (oosh-orchestrator)│"
  echo "├───────────────────────┬─────────────────┤"
  echo "│ Pane 1 - EXPERT       │ Pane 2 - TESTER │"
  echo "│ (oosh-expert)         │ (oosh-tester)   │"
  echo "└───────────────────────┴─────────────────┘"
  echo ""
  echo "Attach with: tmux attach -t $session"
}

hiveMind.team.setup.oosh.completion.session() {
  echo "cursorOrchestrator"
  echo "ooshTeam"
  echo "hivemind"
}

hiveMind.teach() # <pane> <role> # teach an agent its specialized role via SKILL.md
{
  local pane="$1"
  local role="$2"

  if [ -z "$pane" ] || [ -z "$role" ]; then
    error.log "Usage: hiveMind teach <pane> <role>"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  # Get role prompt
  local prompt
  prompt=$(private.hiveMind.get.role.prompt "$role")
  if [ -z "$prompt" ]; then
    error.log "Unknown role: $role"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  info.log "Teaching $role to pane $pane..."

  # Check for SKILL.md in canonical location
  local skill_path="$HIVEMIND_AGENTS_DIR/$role/SKILL.md"

  local full_prompt="$prompt"
  if [ -f "$skill_path" ]; then
    full_prompt="$prompt Also read these key files to understand OOSH: CLAUDE.md, docs/oosh-architecture.md, and $skill_path"
  fi

  # Send the teaching prompt to the pane
  otmux send "$pane" "$full_prompt" Enter

  success.log "Sent role instructions to pane $pane"
  echo "The agent should now learn its role as $role"
}

hiveMind.teach.completion.role() {
  hiveMind.role.list 2>/dev/null || echo "oosh-expert"
}

hiveMind.roles() # # list available agent roles with descriptions
{
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║              Available HiveMind Roles                      ║"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║ Team Roles:                                                ║"
  echo "║   orchestrator        - Train agents, delegate, improve     ║"
  echo "║   scrum-master        - Monitor, approve, enforce roles    ║"
  echo "║   product-owner       - First principles & governance       ║"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║ OOSH Specialists (expert+tester own scripts):              ║"
  echo "║   oosh-expert         - Framework architecture & dev       ║"
  echo "║   oosh-tester         - Testing & quality assurance        ║"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║ References:                                                ║"
  echo "║   script-product-owner - Ownership contract (not a role)  ║"
  echo "║   developer            - Implementation capacity           ║"
  echo "║   task-agent           - Autonomous task execution         ║"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║ General Roles:                                             ║"
  echo "║   orchestrator - Coordinates tasks between agents          ║"
  echo "║   coder        - Implements features and writes code       ║"
  echo "║   tester       - Writes and runs tests                     ║"
  echo "║   reviewer     - Reviews code for quality                  ║"
  echo "╚════════════════════════════════════════════════════════════╝"
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT ROLE MANAGEMENT
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.role.list() # # list all agent roles from .claude/agents/
{
  if [ ! -d "$HIVEMIND_AGENTS_DIR" ]; then
    error.log "Agents directory not found: $HIVEMIND_AGENTS_DIR"
    return 1
  fi

  for role_dir in "$HIVEMIND_AGENTS_DIR"/*/; do
    [ -d "$role_dir" ] || continue
    local role_name=$(basename "$role_dir")
    if [ -f "$role_dir/SKILL.md" ]; then
      echo "$role_name"
    fi
  done
}

hiveMind.role.prompt() # <role> # output the teaching prompt for a role
{
  local role="$1"

  if [ -z "$role" ]; then
    error.log "Usage: hiveMind role.prompt <role>"
    return 1
  fi

  local prompt
  prompt=$(private.hiveMind.get.role.prompt "$role")
  if [ -z "$prompt" ]; then
    error.log "Unknown role: $role"
    return 1
  fi

  local skill_path="$HIVEMIND_AGENTS_DIR/$role/SKILL.md"
  if [ -f "$skill_path" ]; then
    echo "$prompt Also read these key files to understand OOSH: CLAUDE.md, docs/oosh-architecture.md, and $skill_path"
  else
    echo "$prompt"
  fi
}

hiveMind.role.prompt.completion.role() {
  hiveMind.role.list 2>/dev/null
}

hiveMind.role.teach() # <pane> <role> # teach a role to an existing pane (alias for teach)
{
  hiveMind.teach "$@"
}

hiveMind.role.teach.completion.role() {
  hiveMind.role.list 2>/dev/null
}

# ─────────────────────────────────────────────────────────────────────────────
# AGENT LIFECYCLE
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.agent.bootstrap() # <role> <?session:cursorOrchestrator> <?pane> # full bootstrap: create pane, start bash, launch claude, teach role
{
  local role="$1"
  local session="${2:-cursorOrchestrator}"
  local target_pane="$3"

  if [ -z "$role" ]; then
    error.log "Usage: hiveMind agent.bootstrap <role> <?session> <?pane>"
    echo "  Use 'hiveMind role.list' to see available roles"
    return 1
  fi

  # Verify role exists
  local prompt
  prompt=$(private.hiveMind.get.role.prompt "$role")
  if [ -z "$prompt" ]; then
    error.log "Unknown role: $role"
    return 1
  fi

  # Check session exists
  if ! tmux has-session -t "$session" 2>/dev/null; then
    error.log "Session $session not found"
    return 1
  fi

  info.log "Bootstrapping $role agent in session $session..."

  # Step 1: Create pane if not specified
  if [ -z "$target_pane" ]; then
    # Split from the last pane in window 0
    tmux split-window -t "${session}:0" -h
    tmux select-layout -t "${session}:0" tiled
    # Get the new pane index
    local pane_count=$(tmux list-panes -t "${session}:0" -F "#{pane_index}" | sort -n | tail -1)
    target_pane="${session}:0.${pane_count}"
    info.log "Created new pane: $target_pane"
  fi

  # Step 2: Set pane title and export role env var
  private.hiveMind.pane.identify "$target_pane" "$role"

  # Step 3: Start Claude Code
  info.log "Starting Claude Code in pane $target_pane..."
  otmux send.enter "$target_pane" "claude"

  # Step 4: Wait for startup
  info.log "Waiting for Claude Code to initialize..."
  sleep 8

  # Step 5: Teach role
  local full_prompt
  full_prompt=$(hiveMind.role.prompt "$role")
  otmux send.enter "$target_pane" "$full_prompt"

  # Step 6: Wait briefly for processing
  sleep 3

  success.log "Agent $role bootstrapped in pane $target_pane"
  echo "Verify with: hiveMind agent.verify $target_pane"
}

hiveMind.agent.bootstrap.completion.role() {
  hiveMind.role.list 2>/dev/null
}

hiveMind.agent.bootstrap.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.agent.verify() # <pane> # check if agent is alive and processing
{
  local pane="$1"

  if [ -z "$pane" ]; then
    error.log "Usage: hiveMind agent.verify <pane>"
    return 1
  fi

  info.log "Verifying agent in pane $pane..."

  local output
  output=$(tmux capture-pane -t "$pane" -p -S -15 2>/dev/null)

  if [ $? -ne 0 ]; then
    error.log "Cannot capture pane: $pane"
    return 1
  fi

  # Check for processing indicators
  if echo "$output" | grep -qiE "Composing|Musing|Thinking|Cascading|Incubating|Frosting|Reading.*files"; then
    success.log "Agent in $pane is PROCESSING"
    echo "$output" | tail -5
    return 0
  fi

  # Check for idle Claude prompt
  if echo "$output" | grep -qE "^>|❯"; then
    info.log "Agent in $pane is IDLE (ready for input)"
    return 0
  fi

  # Check for Claude Code running
  if echo "$output" | grep -qi "claude"; then
    info.log "Agent in $pane appears ACTIVE"
    echo "$output" | tail -5
    return 0
  fi

  warn.log "Agent in $pane status UNKNOWN"
  echo "$output" | tail -5
  return 1
}

# ─────────────────────────────────────────────────────────────────────────────
# TEAM SETUP
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.team.setup.full() # <?session:cursorOrchestrator> # bootstrap full team: Agent Teacher + Expert + Tester + ScrumMaster
{
  local session="${1:-cursorOrchestrator}"

  info.log "Setting up full OOSH team in session: $session"

  # Check if session already exists
  if tmux has-session -t "$session" 2>/dev/null; then
    warn.log "Session '$session' already exists"
    echo "  Kill it first:  tmux kill-session -t $session"
    echo "  Or attach to it: tmux attach -t $session"
    return 1
  fi

  # ── Create session and pane layout ──────────────────────────────────────
  # ┌─────────────────────────────────────────┐
  # │ Pane 0.0 - AGENT TEACHER                │
  # ├───────────────────────┬─────────────────┤
  # │ Pane 0.1 - EXPERT     │ Pane 0.2 - TEST │
  # │ (oosh-expert)         │ (oosh-tester)   │
  # ├───────────────────────┴─────────────────┤
  # │ Pane 0.3 - SCRUMMASTER                  │
  # └─────────────────────────────────────────┘

  info.log "Creating tmux session: $session"
  otmux new "$session" -d

  # Name window 0
  tmux rename-window -t "${session}:0" "$HIVEMIND_WINDOW_NAME"

  # Split pane 0 vertically → pane 0 (upper) + pane 1 (lower)
  otmux split.v -t "${session}:0.0"

  # Split pane 1 horizontally → pane 1 (lower-left) + pane 2 (lower-right)
  otmux split.h -t "${session}:0.1"

  # Split pane 0 vertically again → add pane 3 below for ScrumMaster
  otmux split.v -t "${session}:0.0"

  # Enable pane border labels (agent names on top border, bold if active)
  tmux set -g pane-border-status top
  tmux set -g pane-border-format " #{?pane_active,#[bold],}#T#[default] "

  # Set pane titles and export role env vars
  private.hiveMind.pane.identify "${session}:0.0" "orchestrator"
  private.hiveMind.pane.identify "${session}:0.1" "scrum-master"
  private.hiveMind.pane.identify "${session}:0.2" "oosh-expert"
  private.hiveMind.pane.identify "${session}:0.3" "oosh-tester"

  # ── Start Claude Code in each pane ─────────────────────────────────────
  info.log "Starting Claude Code in all panes..."

  otmux send.enter "${session}:0.0" "claude"
  sleep 2
  otmux send.enter "${session}:0.1" "claude"
  sleep 2
  otmux send.enter "${session}:0.2" "claude"
  sleep 2
  otmux send.enter "${session}:0.3" "claude"

  # Wait for Claude Code to initialize
  info.log "Waiting for Claude Code agents to initialize..."
  sleep 8

  # ── Send role prompts ──────────────────────────────────────────────────
  info.log "Sending role prompts to agents..."

  otmux send.enter "${session}:0.0" "$(private.hiveMind.get.role.prompt orchestrator)"
  sleep 1
  otmux send.enter "${session}:0.1" "$(private.hiveMind.get.role.prompt scrum-master)"
  sleep 1
  otmux send.enter "${session}:0.2" "$(private.hiveMind.get.role.prompt oosh-expert)"
  sleep 1
  otmux send.enter "${session}:0.3" "$(private.hiveMind.get.role.prompt oosh-tester)"

  # ── Done ───────────────────────────────────────────────────────────────
  success.log "Full OOSH team created in session: $session"
  echo ""
  echo "Pane layout:"
  echo "┌─────────────────────────────────────────┐"
  echo "│ Pane 0.0 - AGENT TEACHER                │"
  echo "├───────────────────────┬─────────────────┤"
  echo "│ Pane 0.2 - EXPERT     │ Pane 0.3 - TEST │"
  echo "│ (oosh-expert)         │ (oosh-tester)   │"
  echo "├───────────────────────┴─────────────────┤"
  echo "│ Pane 0.1 - SCRUMMASTER                  │"
  echo "└─────────────────────────────────────────┘"
  echo ""
  echo "Attach with: tmux attach -t $session"
}

hiveMind.team.setup.full.completion.session() {
  echo "cursorOrchestrator"
  echo "ooshTeam"
  echo "hivemind"
}

hiveMind.team.sonnet1() # <?session:sonnet1m> # start a tmux session with Sonnet 4.5 1M context and correct colors
{
  local base="${1:-sonnet1m}"
  local workdir="/Users/Shared/Workspaces/AI/Claude"

  # Find next available session name: base.1, base.2, etc.
  local session="${base}.1"
  local n=1
  while tmux has-session -t "$session" 2>/dev/null; do
    n=$((n + 1))
    session="${base}.${n}"
  done

  info.log "Creating Sonnet 1M session: $session"

  # Create detached tmux session in the workspace directory
  tmux new-session -d -s "$session" -c "$workdir"
  tmux rename-window -t "${session}:0" "$HIVEMIND_WINDOW_NAME"

  # Start Claude Code with sonnet[1m] model and 256-color mode for Apple Terminal
  # Use CLAUDE_CMD directly to avoid bracket interpretation issues through tmux
  otmux send.enter "${session}:0.0" "FORCE_COLOR=2 \$HOME/.local/bin/claude --model 'sonnet[1m]'"

  # Wait for Claude Code to initialize
  info.log "Waiting for Claude Code to initialize..."
  sleep 8

  # Open a macOS Terminal window attached to the session
  osascript -e "tell application \"Terminal\" to do script \"tmux attach -t $session\""

  success.log "Sonnet 1M session created: $session"
  echo ""
  echo "Model:     sonnet[1m] (claude-sonnet-4-5-20250929[1m])"
  echo "Context:   1M tokens"
  echo "Colors:    256-color (FORCE_COLOR=2)"
  echo "Workspace: $workdir"
  echo ""
  echo "Detach with: Ctrl-b d"
  echo "Reattach:    tmux attach -t $session"
}

hiveMind.team.sonnet1.completion.session() {
  echo "sonnet1m"
  echo "claude1m"
  echo "longctx"
}

hiveMind.pane.titles() # <?session> # set pane border titles for all registered agents from hivemind.roles
{
  local session="$1"

  if [ ! -f "$HIVEMIND_REGISTRY" ]; then
    error.log "No role registry found at $HIVEMIND_REGISTRY"
    return 1
  fi

  # Enable pane border labels if not already on
  tmux set -g pane-border-status top 2>/dev/null
  tmux set -g pane-border-format " #{?pane_active,#[bold],}#T#[default] " 2>/dev/null

  local count=0
  while IFS='|' read -r target role; do
    [ -z "$target" ] || [ -z "$role" ] && continue
    # Filter by session if specified
    if [ -n "$session" ]; then
      case "$target" in "${session}:"*) ;; *) continue ;; esac
    fi
    otmux pane.title "$target" "$role"
    count=$((count + 1))
  done < "$HIVEMIND_REGISTRY"

  console.log "Set $count pane titles from registry"
}
hiveMind.pane.titles.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.team.list() # # list all registered team sessions
{
  if [ ! -f "$HIVEMIND_REGISTRY" ]; then
    error.log "No role registry found at $HIVEMIND_REGISTRY"
    return 1
  fi
  cut -d: -f1 "$HIVEMIND_REGISTRY" | sort -u
}

hiveMind.team.status() # <?session> # show team summary (no arg) or per-pane details (with session)
{
  local session="$1"

  # No argument: show one-line summary per team
  if [ -z "$session" ]; then
    local teams
    teams=$(hiveMind.team.list 2>/dev/null)
    [ -z "$teams" ] && { error.log "No teams registered"; return 1; }

    while read -r team; do
      if ! tmux has-session -t "$team" 2>/dev/null; then
        printf "%-24s (session not running)\n" "$team"
        continue
      fi
      local agent_count=0 active_count=0 idle_count=0
      while IFS='|' read -r target role; do
        [ -z "$target" ] || [ -z "$role" ] && continue
        case "$target" in "${team}:"*) ;; *) continue ;; esac
        agent_count=$((agent_count + 1))
        local activity
        activity=$(private.hiveMind.pane.activity "$target" 2>/dev/null)
        case "$activity" in
          idle|shell) idle_count=$((idle_count + 1)) ;;
          *) active_count=$((active_count + 1)) ;;
        esac
      done < "$HIVEMIND_REGISTRY"
      printf "%-24s %d agents  (%d active, %d idle)\n" "$team" "$agent_count" "$active_count" "$idle_count"
    done <<< "$teams"
    return 0
  fi

  # With argument: show detailed per-pane tree (existing behavior)
  if ! tmux has-session -t "$session" 2>/dev/null; then
    error.log "Session $session not found"
    return 1
  fi

  local pane_lines
  pane_lines=$(tmux list-panes -t "$session" -s \
    -F "#{window_index}.#{pane_index}|#{pane_current_command}" 2>/dev/null)

  local total
  total=$(echo "$pane_lines" | wc -l | tr -d ' ')
  local count=0

  echo "$session"
  while IFS='|' read -r addr pane_cmd; do
    count=$((count + 1))

    local state=""
    local is_claude=false
    case "$pane_cmd" in
      claude*|[0-9].[0-9]*) is_claude=true; state=$(private.hiveMind.pane.activity "${session}:${addr}") ;;
      bash|zsh)
        # Shell reported — but Claude Code may still be running on this TTY.
        # tmux pane_current_command can misreport idle Claude as 'bash'.
        if claudeCode process.running "${session}:${addr}"; then
          is_claude=true
          state=$(private.hiveMind.pane.activity "${session}:${addr}")
        else
          state="shell"
        fi
        ;;
      *) state="$pane_cmd" ;;
    esac

    local prefix="├──"
    [ "$count" -eq "$total" ] && prefix="└──"

    # Look up role from registry; fall back to pane address
    local role
    role=$(private.hiveMind.registry.get "${session}:${addr}")
    local label="${role:-pane $addr}"

    # Get Claude Code session ID — live detection with stored fallback
    local sid_label=""
    if [ "$is_claude" = "true" ]; then
      local sid
      sid=$(claudeCode session.id "${session}:${addr}")
      if [ -n "$sid" ]; then
        sid_label="  [${sid:0:8}]"
        [ -n "$role" ] && private.hiveMind.session.store "$role" "$sid"
      elif [ -n "$role" ]; then
        sid=$(private.hiveMind.session.lookup "$role")
        [ -n "$sid" ] && sid_label="  [${sid:0:8}*]"
      fi
    fi

    echo "$prefix $addr  $label ($state)${sid_label}"
  done <<< "$pane_lines"
}

hiveMind.team.list.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}
hiveMind.team.status.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.team.sweep() # <session> # structured one-line-per-pane status of all registered agents
{
  local session="$1"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.sweep <session>"
    return 1
  fi

  if [ ! -f "$HIVEMIND_REGISTRY" ]; then
    error.log "No role registry found at $HIVEMIND_REGISTRY"
    return 1
  fi

  # Identify own pane to skip self
  local own_pane=""
  [ -n "$TMUX_PANE" ] && own_pane="$TMUX_PANE"

  while IFS='|' read -r target role; do
    [ -z "$target" ] || [ -z "$role" ] && continue
    case "$target" in "${session}:"*) ;; *) continue ;; esac

    local addr="${target#*:}"

    # Skip self
    if [ -n "$own_pane" ]; then
      local pane_id
      pane_id=$(tmux display-message -t "$target" -p "#{pane_id}" 2>/dev/null)
      [ "$pane_id" = "$own_pane" ] && continue
    fi

    # Capture last 15 lines
    local content
    content=$(otmux pane.capture "$target" 15 2>/dev/null)

    # Determine state and details
    local state="IDLE"
    local details=""

    # Check for empty/unreachable pane
    local trimmed
    trimmed=$(echo "$content" | tr -d '[:space:]')
    if [ -z "$trimmed" ]; then
      state="UNKNOWN"
      printf "%-5s %-22s %s\n" "$addr" "$role" "$state"
      continue
    fi

    # PERMISSION — "Allow"+"Deny" or "Do you want to proceed?"
    if echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
      state="PERMISSION"
      details=$(echo "$content" | grep -oE '"[^"]*"' | tail -1)
    elif echo "$content" | grep -q 'Do you want to proceed?'; then
      state="PERMISSION"

    # CONTEXT_LOW — "Context left until auto-compact: N%" or "N% remaining"
    elif echo "$content" | grep -qE 'auto-compact: [0-9]+%'; then
      state="CONTEXT_LOW"
      details=$(echo "$content" | grep -oE '[0-9]+%' | tail -1)
    elif echo "$content" | grep -qE '[0-9]+% remaining'; then
      state="CONTEXT_LOW"
      details=$(echo "$content" | grep -oE '[0-9]+%' | tail -1)

    # ACTIVE — creative verb spinner or "thinking"
    elif echo "$content" | grep -qiE 'Composing|Musing|Thinking|Cascading|Incubating|Frosting|Misting|Brewing|Baking|Cooking|Simmering|Distilling|Weaving|Crafting|Processing|Reading|Writing|Editing|Searching|Running'; then
      state="ACTIVE"
      details=$(echo "$content" | grep -oiE 'Composing|Musing|Thinking|Cascading|Incubating|Frosting|Misting|Brewing|Baking|Cooking|Simmering|Distilling|Weaving|Crafting|Processing|Reading|Writing|Editing|Searching|Running' | tail -1)
      # Try to capture elapsed time if present (e.g. "(14m)")
      local elapsed
      elapsed=$(echo "$content" | grep -oE '\([0-9]+[ms]\)' | tail -1)
      [ -n "$elapsed" ] && details="$details $elapsed"

    # COMPLETED — past-tense verb (Baked, Brewed, Cooked, etc.)
    elif echo "$content" | grep -qiE 'Baked|Brewed|Cooked|Simmered|Distilled|Woven|Crafted|Composed|Frosted|Incubated|Cascaded'; then
      state="COMPLETED"

    else
      # Check last non-empty line for prompt state
      local last_line
      last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)

      # INPUT — text after prompt
      if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]+.+'; then
        state="INPUT"
        details=$(echo "$last_line" | sed -E 's/^[[:space:]]*(>|❯)[[:space:]]+//')
        # Truncate long input
        [ ${#details} -gt 40 ] && details="${details:0:37}..."
        details="\"$details\""

      # IDLE — clean prompt
      elif echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]*$'; then
        state="IDLE"

      # Shell escaped
      elif echo "$last_line" | grep -qE '^[[:space:]]*\$[[:space:]]*$'; then
        state="SHELL"

      # Default: unknown activity
      else
        state="ACTIVE"
      fi
    fi

    if [ -n "$details" ]; then
      printf "%-5s %-22s %-14s %s\n" "$addr" "$role" "$state" "$details"
    else
      printf "%-5s %-22s %s\n" "$addr" "$role" "$state"
    fi
  done < "$HIVEMIND_REGISTRY"
}
hiveMind.team.sweep.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.team.loop() # <session> <?interval:30> # continuous team.sweep at interval
{
  local session="$1"
  local interval="${2:-30}"

  if [ -z "$session" ]; then
    error.log "Usage: hiveMind team.loop <session> <?interval>"
    return 1
  fi

  console.log "Team loop: $session every ${interval}s (Ctrl+C to stop)"

  while true; do
    echo ""
    echo "── team.sweep @ $(date '+%H:%M:%S') ──────────────────────────────"
    hiveMind.team.sweep "$session"
    sleep "$interval"
  done
}
hiveMind.team.loop.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}
hiveMind.team.loop.completion.interval() {
  echo "15"
  echo "30"
  echo "60"
}

hiveMind.monitor() # <?name-or-lines:5> <?session-or-lines> # capture pane output; name=specific agent, number=line count for all panes
{
  local first="${1:-5}"
  local second="$2"

  # If first arg is not a number, treat as agent name (monitor single agent)
  if [ -n "$first" ] && ! echo "$first" | grep -qE '^[0-9]+$'; then
    local name="$first"
    local lines="${second:-5}"
    local target
    target=$(hiveMind.resolve "$name")
    if [ $? -ne 0 ]; then
      return 1
    fi

    echo "───────────────────────────────────────────────────────────"
    echo " $name ($target) — last $lines lines"
    echo "───────────────────────────────────────────────────────────"
    otmux pane.capture "$target" "$lines"
    echo ""
    return 0
  fi

  # All-pane monitoring (existing behavior)
  local lines="$first"
  local session="${second:-${HIVEMIND_SESSION:-cursorOrchestrator}}"

  if ! tmux has-session -t "$session" 2>/dev/null; then
    error.log "Session '$session' not found"
    return 1
  fi

  local own_pane=""
  if [ -n "$TMUX_PANE" ]; then
    own_pane="$TMUX_PANE"
  fi

  echo "═══════════════════════════════════════════════════════════"
  echo " hiveMind monitor — session: $session (last $lines lines)"
  echo "═══════════════════════════════════════════════════════════"

  tmux list-panes -t "$session" -F "#{pane_id}|#{pane_index}|#{pane_title}" 2>/dev/null | \
    while IFS='|' read -r pane_id pane_idx pane_title; do
      if [ -n "$own_pane" ] && [ "$pane_id" = "$own_pane" ]; then
        continue
      fi

      local label="${pane_title:-pane $pane_idx}"
      echo "───────────────────────────────────────────────────────────"
      echo " [$pane_idx] $label"
      echo "───────────────────────────────────────────────────────────"
      otmux pane.capture "${session}:0.${pane_idx}" "$lines"
      echo ""
    done

  info.log "Monitored $session"
  return 0
}

hiveMind.monitor.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.monitor.approve() # <pane> <?option:2> # approve a permission prompt in a pane
{
  local pane="$1"
  local option="${2:-2}"

  if [ -z "$pane" ]; then
    error.log "Usage: hiveMind monitor.approve <pane> <?option:2>"
    return 1
  fi

  info.log "Sending approval option $option to pane $pane..."
  otmux send "$pane" "$option"
  success.log "Sent option $option to pane $pane"
}

hiveMind.monitor.approve.completion.option() {
  echo "2"
  echo "1"
  echo "3"
}

# ─────────────────────────────────────────────────────────────────────────────
# SWEEP & UNBLOCK
# ─────────────────────────────────────────────────────────────────────────────

private.hiveMind.sweep.detect() {
  # Enhanced blocker detection — returns status|action
  # Statuses: active, idle, permission, accept-edits, context-warning, just-compacted,
  #           queued, rate-limit, autocomplete, shell-escaped, shell, unknown
  # Actions: none, enter, escape
  local target="$1"
  [ -z "$target" ] && echo "unknown|none" && return 1

  local content
  content=$(tmux capture-pane -t "$target" -p -S -10 2>/dev/null)

  local trimmed
  trimmed=$(echo "$content" | tr -d '[:space:]')
  [ -z "$trimmed" ] && echo "unknown|none" && return 0

  # Background tasks overlay — more specific than permission, check first
  if echo "$content" | grep -q 'Background tasks'; then
    echo "overlay|escape"
    return 0
  fi

  # Git diff/status panel — needs Escape to close
  if echo "$content" | grep -qE 'files \+[0-9]+ -[0-9]+|Uncommitted changes|git diff|Esc close|PR #[0-9]+'; then
    echo "panel|escape"
    return 0
  fi

  # Permission prompt: "Allow" + "Deny" or "Do you want to proceed?" with Yes/No
  if echo "$content" | grep -q 'Allow' && echo "$content" | grep -q 'Deny'; then
    echo "permission|enter"
    return 0
  fi
  if echo "$content" | grep -q 'Do you want to proceed?' && echo "$content" | grep -qE '^\s*(❯\s*)?[0-9]+\.\s*(Yes|No)'; then
    echo "permission|enter"
    return 0
  fi

  # Rate limit: message about limits
  if echo "$content" | grep -qiE 'rate.limit|usage.limit|try again.*(in |later)|too many requests'; then
    echo "rate-limit|enter"
    return 0
  fi

  # Accept edits prompt: ⏵⏵ accept edits on · N bash(es)
  if echo "$content" | grep -q '⏵⏵ accept'; then
    local edit_count
    edit_count=$(echo "$content" | grep -oE '[0-9]+ bash' | head -1 | grep -oE '[0-9]+')
    [ -z "$edit_count" ] && edit_count=0
    echo "accept-edits|enter|$edit_count"
    return 0
  fi

  # Context warning: "Context left until auto-compact: N%"
  if echo "$content" | grep -qE 'auto-compact: [0-9]+%'; then
    echo "context-warning|none"
    return 0
  fi

  # Just compacted: "Compacted" in recent output
  if echo "$content" | grep -qiE 'Compacted|auto-compact.*completed'; then
    echo "just-compacted|none"
    return 0
  fi

  # Autocomplete stuck: completion menu visible (e.g. /compact dropdown)
  if echo "$content" | grep -qE '^\s*/\w+.*─'; then
    echo "autocomplete|escape"
    return 0
  fi

  local last_line
  last_line=$(echo "$content" | sed '/^[[:space:]]*$/d' | tail -1)

  # Shell-escaped: bare $ prompt (agent left Claude Code)
  if echo "$last_line" | grep -qE '^[[:space:]]*\$[[:space:]]*$'; then
    echo "shell-escaped|none"
    return 0
  fi

  # Idle: clean prompt (just ❯ or > with no text after)
  if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]*$'; then
    echo "idle|none"
    return 0
  fi

  # Queued: text after prompt not submitted
  if echo "$last_line" | grep -qE '^[[:space:]]*(>|❯)[[:space:]]+.+'; then
    echo "queued|enter"
    return 0
  fi

  # Active (generating/running tools/indeterminate)
  echo "active|none"
  return 0
}

private.hiveMind.unblock.pane() {
  # Detect and resolve blocker on a single pane
  local target="$1"
  local label="$2"

  local result
  result=$(private.hiveMind.sweep.detect "$target")
  local status="${result%%|*}"
  local action="${result#*|}"

  # Parse result — may have 3 fields: status|action|count
  local count="${result#*|}"
  count="${count#*|}"  # Get third field if present
  [[ "$count" =~ ^[0-9]+$ ]] || count=0

  case "$status" in
    permission)
      # Select option 2 ("Yes, and don't ask again") — Down then Enter
      otmux send "$target" Down
      sleep 0.3
      otmux send "$target" Enter
      console.log "Unblocked $label ($status) → Down+Enter (option 2)"
      ;;
    accept-edits)
      # Accept pending edits — Enter for each stacked edit + base
      local i
      for ((i=0; i<=count; i++)); do
        otmux send "$target" Enter
        sleep 0.5
      done
      console.log "Unblocked $label ($status) → $((count+1))x Enter (accept edits)"
      ;;
    overlay|panel)
      # Dismiss overlay or git panel
      otmux send "$target" Escape
      console.log "Unblocked $label ($status) → Escape"
      ;;
    *)
      case "$action" in
        enter)
          otmux send "$target" Enter
          console.log "Unblocked $label ($status) → Enter"
          ;;
        escape)
          otmux send "$target" Escape
          sleep 0.1
          otmux send "$target" Enter
          console.log "Unblocked $label ($status) → Escape+Enter"
          ;;
        none) ;;
      esac
      ;;
  esac
}

hiveMind.sweep() # <?session:cursorOrchestrator> # show last lines of each agent pane
{
  local session="${1:-cursorOrchestrator}"

  if [ ! -f "$HIVEMIND_REGISTRY" ]; then
    error.log "No role registry found at $HIVEMIND_REGISTRY"
    return 1
  fi

  while IFS='|' read -r target role; do
    [ -z "$target" ] || [ -z "$role" ] && continue
    case "$target" in "${session}:"*) ;; *) continue ;; esac

    local addr="${target#*:}"
    echo "=== $role ($addr) ==="
    otmux pane.capture "$target" 8
    echo ""
  done < "$HIVEMIND_REGISTRY"
}
hiveMind.sweep.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

hiveMind.sweep.cycle() # <?session> <?interval:0> # run one sweep+unblock cycle with optional sleep
{
  local session="${1:-$(tmux display-message -p '#{session_name}' 2>/dev/null)}"
  local interval="${2:-0}"

  [ "$interval" -gt 0 ] 2>/dev/null && sleep "$interval"

  hiveMind.sweep "$session"
  hiveMind.unblock all "$session"
}
hiveMind.sweep.cycle.completion() { :; }

hiveMind.auto.commit() # <?message> # auto-commit uncommitted changes if any exist
{
  local msg="${1:-Auto-commit: cycle checkpoint $(date '+%Y-%m-%d %H:%M')}"

  # Check for uncommitted changes (staged or unstaged tracked files)
  if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
    git add -A
    git commit -m "$msg

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
    git push 2>/dev/null &
    console.log "Auto-committed: $msg"
    return 0
  fi

  # Check for untracked session files
  local untracked
  untracked=$(git ls-files --others --exclude-standard -- session/ 2>/dev/null | head -5)
  if [ -n "$untracked" ]; then
    git add session/
    git commit -m "$msg

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
    git push 2>/dev/null &
    console.log "Auto-committed session files: $msg"
    return 0
  fi

  console.log "Nothing to commit"
  return 0
}
hiveMind.auto.commit.completion() { :; }

hiveMind.cycle.full() # <?session> # run full monitoring cycle: sweep + unblock + context check + auto-commit
{
  local session="${1:-$(tmux display-message -p '#{session_name}' 2>/dev/null)}"

  # Step 1: Sweep and unblock
  console.log "Cycle: sweep $session"
  hiveMind.sweep "$session" 2>/dev/null
  hiveMind.unblock all "$session" 2>/dev/null

  # Step 2: Check context for all panes
  console.log "Cycle: context check"
  local panes
  panes=$(tmux list-panes -t "${session}:0" -F "#{pane_index}" 2>/dev/null)
  for pane in $panes; do
    local ctx
    ctx=$(claudeCode context.read "${session}:0.${pane}" 2>/dev/null)
    if [ -n "$ctx" ] && [ "$ctx" != "unknown" ]; then
      # Alert if below 25%
      local remaining
      remaining=$(echo "$ctx" | grep -oE '[0-9]+' | head -1)
      if [ -n "$remaining" ] && [ "$remaining" -lt 25 ] 2>/dev/null; then
        console.log "ALERT: ${session}:0.${pane} context at ${remaining}%"
      fi
    fi
  done

  # Step 3: Auto-commit if changes
  console.log "Cycle: auto-commit"
  hiveMind.auto.commit "Cycle checkpoint $(date '+%H:%M')" 2>/dev/null

  console.log "Cycle complete for $session"
}
hiveMind.cycle.full.completion() { :; }

hiveMind.dashboard() # <?session:cursorOrchestrator> # DEPRECATED — use scrumMaster dashboard
{
  warn.log "hiveMind dashboard is deprecated — use: scrumMaster dashboard $*"
  "$OOSH_DIR/scrumMaster" dashboard "$@"
}
hiveMind.dashboard.completion() { :; }

hiveMind.unblock() # <name> <?session:cursorOrchestrator> # detect and resolve stuck prompt; name=agent name or 'all'
{
  local name="$1"
  if [ -z "$name" ]; then
    error.log "Usage: hiveMind unblock <name|all>"
    return 1
  fi

  local session="${2:-cursorOrchestrator}"

  if [ "$name" = "all" ]; then
    while IFS='|' read -r target role; do
      [ -z "$target" ] || [ -z "$role" ] && continue
      case "$target" in "${session}:"*) ;; *) continue ;; esac
      private.hiveMind.unblock.pane "$target" "$role"
    done < "$HIVEMIND_REGISTRY"
    return 0
  fi

  # Single agent
  local target
  target=$(hiveMind.resolve "$name")
  [ $? -ne 0 ] && return 1
  private.hiveMind.unblock.pane "$target" "$name"
}
hiveMind.unblock.completion.name() {
  hiveMind.resolve.completion.name
  echo "all"
}

hiveMind.monitor.cycle() # <?session> # capture, detect, and unblock all panes in one call
{
  local session="${1:-$(tmux display-message -p '#{session_name}' 2>/dev/null)}"

  # Get all panes
  local panes
  panes=$(tmux list-panes -t "${session}:0" -F "#{pane_index}" 2>/dev/null)
  [ -z "$panes" ] && { error.log "No panes found for $session"; return 1; }

  local pane status
  for pane in $panes; do
    local result
    result=$(private.hiveMind.sweep.detect "${session}:0.${pane}")
    status="${result%%|*}"
    case "$status" in
      active|idle|unknown) ;; # skip
      *) private.hiveMind.unblock.pane "${session}:0.${pane}" "pane-${pane}" ;;
    esac
  done
}
hiveMind.monitor.cycle.completion() { :; }

hiveMind.sweep.loop() # <?seconds:30> # continuous sweep + unblock all sessions at interval
{
  local interval="${1:-30}"

  console.log "Starting sweep loop every ${interval}s across all sessions (Ctrl+C to stop)"

  while true; do
    echo ""
    echo "── sweep @ $(date '+%H:%M:%S') ──────────────────────────────────"
    local teams
    teams=$(hiveMind.team.list 2>/dev/null)
    if [ -n "$teams" ]; then
      while read -r session; do
        hiveMind.sweep "$session"
        hiveMind.unblock all "$session"
      done <<< "$teams"
    fi
    sleep "$interval"
  done
}
hiveMind.sweep.loop.completion.seconds() {
  echo "15"
  echo "30"
  echo "60"
  echo "120"
}
hiveMind.sweep.loop.completion.session() {
  tmux list-sessions -F "#{session_name}" 2>/dev/null
}

# ─────────────────────────────────────────────────────────────────────────────
# WATCHDOG — external sweep/unblock loop (runs outside Claude Code)
# ─────────────────────────────────────────────────────────────────────────────

: ${HIVEMIND_WATCHDOG_PID_FILE:=/tmp/hivemind.watchdog.pid}
: ${HIVEMIND_WATCHDOG_LOG:=/tmp/hivemind.watchdog.log}
: ${HIVEMIND_WATCHDOG_HEARTBEAT:=/tmp/hivemind.watchdog.heartbeat}

hiveMind.watchdog() # <?seconds:30> # start external sweep+unblock loop in a new tmux pane
{
  local interval="${1:-30}"

  # Check if already running
  if hiveMind.watchdog.status > /dev/null 2>&1; then
    console.log "Watchdog already running (PID $(cat "$HIVEMIND_WATCHDOG_PID_FILE"))"
    return 0
  fi

  local session
  session=$(tmux display-message -p "#{session_name}" 2>/dev/null)
  [ -z "$session" ] && { error.log "Not inside a tmux session"; return 1; }

  # Spawn a new pane running plain bash (no Claude Code = no permission prompts)
  tmux split-window -t "${session}:0" -v -l 5 \
    "cd '$OOSH_DIR' && echo \$\$ > '$HIVEMIND_WATCHDOG_PID_FILE' && while true; do touch '$HIVEMIND_WATCHDOG_HEARTBEAT'; echo \"── watchdog @ \$(date '+%H:%M:%S') ──\" >> '$HIVEMIND_WATCHDOG_LOG'; for team in \$(hiveMind team.list 2>/dev/null); do hiveMind unblock all \"\$team\" >> '$HIVEMIND_WATCHDOG_LOG' 2>&1; done; sleep $interval; done"

  # Lock the pane title
  sleep 0.5
  local watchdog_pane
  watchdog_pane=$(tmux list-panes -t "${session}:0" -F "#{pane_index}" | tail -1)
  otmux pane.lock "${session}:0.${watchdog_pane}" "watchdog" 2>/dev/null

  console.log "Watchdog started in ${session}:0.${watchdog_pane} (every ${interval}s)"
  console.log "Log: $HIVEMIND_WATCHDOG_LOG"
}
hiveMind.watchdog.completion() { :; }

hiveMind.watchdog.stop() # # stop the external watchdog loop
{
  if [ ! -f "$HIVEMIND_WATCHDOG_PID_FILE" ]; then
    error.log "No watchdog PID file found"
    return 1
  fi

  local pid
  pid=$(cat "$HIVEMIND_WATCHDOG_PID_FILE")
  if kill -0 "$pid" 2>/dev/null; then
    kill "$pid" 2>/dev/null
    rm -f "$HIVEMIND_WATCHDOG_PID_FILE" "$HIVEMIND_WATCHDOG_HEARTBEAT"
    console.log "Watchdog stopped (PID $pid)"
  else
    rm -f "$HIVEMIND_WATCHDOG_PID_FILE" "$HIVEMIND_WATCHDOG_HEARTBEAT"
    console.log "Watchdog was not running (stale PID file cleaned)"
  fi
}
hiveMind.watchdog.stop.completion() { :; }

hiveMind.watchdog.status() # # check if watchdog is running
{
  if [ ! -f "$HIVEMIND_WATCHDOG_PID_FILE" ]; then
    echo "not running"
    return 1
  fi

  local pid
  pid=$(cat "$HIVEMIND_WATCHDOG_PID_FILE")
  if kill -0 "$pid" 2>/dev/null; then
    echo "running (PID $pid)"
    return 0
  else
    echo "not running (stale PID file)"
    rm -f "$HIVEMIND_WATCHDOG_PID_FILE"
    return 1
  fi
}
hiveMind.watchdog.status.completion() { :; }

hiveMind.watchdog.supervisor() # <?interval:30> # check watchdog health and restart if dead/stale
{
  local watchdog_interval="${1:-30}"
  local max_stale=$((watchdog_interval * 3))  # 3x interval = stale

  # Check if watchdog is running (PID alive)
  if ! hiveMind.watchdog.status > /dev/null 2>&1; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') SUPERVISOR: Watchdog dead, restarting..." >> "$HIVEMIND_WATCHDOG_LOG"
    hiveMind.watchdog "$watchdog_interval"
    return $?
  fi

  # Check heartbeat freshness (detect stuck watchdog)
  if [ -f "$HIVEMIND_WATCHDOG_HEARTBEAT" ]; then
    local last_beat now age
    last_beat=$(stat -f %m "$HIVEMIND_WATCHDOG_HEARTBEAT" 2>/dev/null)  # macOS stat
    now=$(date +%s)
    age=$(( now - last_beat ))
    if [ "$age" -gt "$max_stale" ]; then
      echo "$(date '+%Y-%m-%d %H:%M:%S') SUPERVISOR: Watchdog stale (${age}s > ${max_stale}s), restarting..." >> "$HIVEMIND_WATCHDOG_LOG"
      hiveMind.watchdog.stop
      hiveMind.watchdog "$watchdog_interval"
      return $?
    fi
  fi

  console.log "Watchdog healthy"
  return 0
}
hiveMind.watchdog.supervisor.completion() { :; }

# ─────────────────────────────────────────────────────────────────────────────
# USAGE
# ─────────────────────────────────────────────────────────────────────────────

hiveMind.usage()
{
  local this=${0##*/}
  echo "You started"
  echo "$0

  hiveMind - Multi-agent orchestrator for oosh
  Manages REAL Claude Code agents in tmux panes

  Usage:
  $this command     Description
  ─────────────────────────────────────────────────────
  INITIALIZATION:
      init              initialize hivemind with agents
      attach            attach to hivemind session
      detach            detach from session
      join              rejoin agent's Claude session by role name
      kill              shutdown hivemind completely

  AGENTS:
      spawn             spawn a new agent with pane
      list              list all agents
      status            one-line session overview
      focus             focus on agent pane
      resolve           resolve agent name to pane target
      panes             list all agent panes

  ROLES:
      roles             show all role descriptions
      role.list         list roles from .claude/agents/
      role.prompt       output teaching prompt for role
      role.teach        teach role to existing pane

  AGENT LIFECYCLE:
      agent.bootstrap   full bootstrap: pane + claude + teach
      agent.verify      check if agent is alive

  TEAM:
      team.setup.oosh   create 3-pane oosh team
      team.setup.full   create 4-pane full team
      team.status       show team members as tree
      team.sweep        structured one-line-per-pane status
      team.loop         continuous team.sweep at interval
      pane.titles       set border labels for all agents
      teach             teach agent a role

  MONITORING:
      monitor           capture pane output (by name or all)
      monitor.approve   approve permission prompt in pane
      sweep             batch-capture all panes, return status table
      unblock <name>    detect and resolve stuck prompts (or 'all')
      sweep.loop <sec>  continuous sweep + unblock at interval

  MESSAGING:
      agent.send        send message via best available channel (transport-independent)
      send              send raw keys to agent by name (no Enter)
      send.enter        send message to agent by name (with Enter)
      task              send task to specific agent
      broadcast         broadcast message to all agents

  DISCOVERY:
      discover          discover agents as JSON

  CLAUDE CODE:
      claude            interact with agent via Claude Code
      pane.create       create tmux pane for agent
  ─────────────────────────────────────────────────────

  Examples:
    $this status                      # one-line overview
    $this team.status                 # tree view of all agents
    $this resolve expert              # find expert's pane target
    $this agent.send expert 'fix bug'  # transport-independent (prefers pane, falls back to API)
    $this send.enter expert 'fix bug' # send message by name (with Enter)
    $this send expert Down Enter      # send raw keys by name
    $this monitor expert              # capture 5 lines from expert
    $this monitor expert 10           # capture 10 lines from expert
    $this monitor                     # capture 5 lines from all panes
    $this monitor 10                  # capture 10 lines per pane
    $this role.list                   # list available roles
    $this agent.bootstrap oosh-expert # bootstrap an expert
    $this join product-owner          # rejoin PO's Claude session
    $this team.setup.full             # create full team
  "
}

hiveMind.help() # # show usage information
{
  hiveMind.usage
}

hiveMind.start()
{
  #echo "sourcing init"
  source this

  # Ensure HIVEMIND_AGENTS_DIR is set after 'source this' provides OOSH_DIR
  : ${HIVEMIND_AGENTS_DIR:=${OOSH_DIR}/../../../.claude/agents}

  if [ -z "$1" ]; then
    hiveMind.status
    return 0
  fi

  this.start "$@"
}

hiveMind.start "$@"
