#!/usr/bin/env bash
# _human-session — run a human's interactive agent runtime where nothing on this machine
# can kill it.
#
#   cgroup    human.slice: no memory/pid/cpu ceiling, MemoryMin reservation, oomd-exempt
#   oom       oom_score_adj -900 on the tmux server, inherited by every pane
#   lifetime  a tmux server owned by systemd, so a terminal or gnome-terminal-server
#             crash detaches the session instead of killing it
#
# usage: _human-session <runtime-binary> [arg...]
# Reached only from _tmpjail-shim.sh, which classifies the caller with lib/session-class.sh.
# Nothing here is a security boundary: it is reached ONLY after that classification says
# human, and it grants no capability an interactive shell does not already have.
set -uo pipefail

SELF_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
STATE_DIR="$HOME/.local/state/human-session"
SOCK="$STATE_DIR/tmux.sock"
CONF="$SELF_DIR/../lib/human-session.tmux.conf"
SLICE=human.slice
SERVER_UNIT=human-session-tmux.service
OOM_SCORE=-900

hs_note() { printf '[human-session] %s\n' "$*" >&2; }

(( $# >= 1 )) || { hs_note "usage: _human-session <runtime> [arg...]"; exit 64; }

RUNTIME="$1"
shift

case "${PYTHONPYCACHEPREFIX:-}" in
  %h) export PYTHONPYCACHEPREFIX="$HOME" ;;
  %h/*) export PYTHONPYCACHEPREFIX="$HOME/${PYTHONPYCACHEPREFIX#%h/}" ;;
esac

mkdir -p "$STATE_DIR" || { hs_note "cannot create $STATE_DIR"; exit 73; }
chmod 700 "$STATE_DIR" 2>/dev/null

tmux_() { tmux -S "$SOCK" "$@"; }

server_pid() { tmux_ display-message -p '#{pid}' 2>/dev/null; }

server_in_slice() { # 0 when $1 belongs to human.slice
  local pid="$1" cg
  [[ "$pid" =~ ^[0-9]+$ ]] || return 1
  read -r cg <"/proc/$pid/cgroup" 2>/dev/null || return 1
  [[ "$cg" == *"/$SLICE/"* ]]
}

start_server() {
  systemctl --user reset-failed "$SERVER_UNIT" 2>/dev/null
  systemd-run --user --quiet --collect \
    --unit="$SERVER_UNIT" \
    --slice="$SLICE" \
    --service-type=forking \
    -p OOMPolicy=continue \
    -p KillMode=process \
    -p TasksMax=infinity \
    -p MemoryMax=infinity \
    -p MemoryHigh=infinity \
    -p MemorySwapMax=infinity \
    -p CPUWeight=1000 \
    -p ManagedOOMPreference=omit \
    -- /usr/bin/tmux -S "$SOCK" -f "$CONF" start-server || return 1

  local i
  for ((i = 0; i < 50; i++)); do
    server_pid >/dev/null 2>&1 && return 0
    sleep 0.1
  done
  return 1
}

# Only root may lower oom_score_adj; the user manager clamps -900 to its own 100. Children
# of the server inherit whatever is set here, so this runs once per server, not per session.
shield_server_oom() {
  local pid="$1" current
  read -r current <"/proc/$pid/oom_score_adj" 2>/dev/null || current=""
  [[ "$current" == "$OOM_SCORE" ]] && return 0
  command -v deck-sudo >/dev/null 2>&1 || return 1
  deck-sudo sh -c "printf '%s' $OOM_SCORE > /proc/$pid/oom_score_adj" >/dev/null 2>&1 || return 1
  read -r current <"/proc/$pid/oom_score_adj" 2>/dev/null || current=""
  [[ "$current" == "$OOM_SCORE" ]]
}

# Runs the runtime in human.slice without tmux. Used when tmux is unusable: losing terminal
# durability is bad, losing the cgroup protection as well would be worse.
exec_without_tmux() { # $1=reason, rest=runtime argv
  hs_note "$1 — running in $SLICE without tmux (session will NOT survive terminal death)"
  shift
  export HUMAN_SESSION_ACTIVE=1
  export AGENT_BUILD_SCOPE_ACTIVE=1
  exec systemd-run --user --scope --quiet --collect \
    --slice="$SLICE" \
    --unit="human-session-$$-${RANDOM}" \
    --same-dir \
    -p OOMPolicy=continue \
    -p TasksMax=infinity \
    -p MemoryMax=infinity \
    -p MemorySwapMax=infinity \
    -- "$HOME/.claude/bin/tmpjail" "$RUNTIME" "$@"
}

command -v tmux >/dev/null 2>&1 || exec_without_tmux "tmux not installed" "$@"
[[ -r "$CONF" ]] || exec_without_tmux "tmux config missing at $CONF" "$@"

SERVER_PID="$(server_pid)"
if [[ ! "$SERVER_PID" =~ ^[0-9]+$ ]]; then
  start_server || exec_without_tmux "tmux server would not start in $SLICE" "$@"
  SERVER_PID="$(server_pid)"
fi

# A server the user started by hand lives in the terminal's cgroup, so attaching to it would
# silently hand back every property this launcher exists to guarantee.
server_in_slice "$SERVER_PID" || exec_without_tmux "existing tmux server on $SOCK is not in $SLICE" "$@"

shield_server_oom "$SERVER_PID" \
  || hs_note "could not set oom_score_adj=$OOM_SCORE on the tmux server (needs root via deck-sudo); session keeps every other protection"

LEDGER_TARGET_PREBOUND=0
if [[ -n "${AGENT_LEDGER_ID:-}" && "${AGENT_LEDGER_HUMAN_TARGET:-}" == "$AGENT_LEDGER_ID" ]] &&
  [[ "$AGENT_LEDGER_ID" =~ ^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$ ]]; then
  NAME="$AGENT_LEDGER_ID"
  LEDGER_TARGET_PREBOUND=1
else
  NAME="$(basename "$PWD")-$(date +%H%M%S)"
  NAME="${NAME//[^A-Za-z0-9_-]/_}"
fi

TZ=UTC printf -v HUMAN_STARTED '%(%Y-%m-%dT%H:%M:%SZ)T' -1
if [[ "$LEDGER_TARGET_PREBOUND" == 1 ]] &&
  [[ "${AGENT_LEDGER_HUMAN_REAP_ID:-}" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ ]]; then
  REAP_ID="$AGENT_LEDGER_HUMAN_REAP_ID"
else
  REAP_ID="$(</proc/sys/kernel/random/uuid)"
fi

# The runtime's environment (account routing from cld, API base URLs, CLAUDE_HOME) must
# reach the pane verbatim; a tmux pane otherwise inherits the SERVER's environment, which
# came from systemd and has none of it.
umask 077
SPAWN="$(mktemp "$STATE_DIR/spawn.XXXXXXXX.sh")" || exec_without_tmux "cannot write spawn file" "$@"
{
  printf '#!/usr/bin/env bash\n'
  export -p
  printf 'export HUMAN_SESSION_ACTIVE=1\n'
  printf 'export AGENT_BUILD_SCOPE_ACTIVE=1\n'
  printf 'cd %q || exit 1\n' "$PWD"
  # Unlinking before exec keeps the environment (which carries credentials) on disk for the
  # few milliseconds the pane needs to read it; the open fd survives the unlink.
  printf 'rm -f %q\n' "$SPAWN"
  printf 'exec systemd-run --user --scope --quiet --collect --slice=%q --unit=%q --same-dir' \
    "$SLICE" "human-agent-$REAP_ID"
  printf ' -p OOMPolicy=continue -p TasksMax=infinity -p MemoryMax=infinity -p MemorySwapMax=infinity -- %q %q' \
    "$HOME/.claude/bin/tmpjail" "$RUNTIME"
  for a in "$@"; do printf ' %q' "$a"; done
  printf '\n'
} >"$SPAWN"
chmod 700 "$SPAWN"

if ! tmux_ new-session -d -s "$NAME" -c "$PWD" -x "$(tput cols 2>/dev/null || echo 200)" \
     -y "$(tput lines 2>/dev/null || echo 50)" "exec bash $SPAWN"; then
  rm -f "$SPAWN"
  exec_without_tmux "tmux could not create a new session named $NAME" "$@"
fi

if ! tmux_ set-option -t "$NAME" @human_cwd "$PWD" \; \
     set-option -t "$NAME" @human_runtime "${RUNTIME##*/}" \; \
     set-option -t "$NAME" @human_started "$HUMAN_STARTED" \; \
     set-option -t "$NAME" @agent_reap_identity "$REAP_ID"; then
  hs_note "session metadata was not installed completely — this session will never be auto-reaped"
fi

INSTALLED_REAP_ID="$(tmux_ show-options -v -t "$NAME" @agent_reap_identity 2>/dev/null || :)"
if [[ "$INSTALLED_REAP_ID" != "$REAP_ID" ]]; then
  tmux_ set-option -t "$NAME" @agent_reap_identity "$REAP_ID" >/dev/null 2>&1 || :
  INSTALLED_REAP_ID="$(tmux_ show-options -v -t "$NAME" @agent_reap_identity 2>/dev/null || :)"
fi
if [[ "$INSTALLED_REAP_ID" != "$REAP_ID" ]]; then
  INSTALLED_REAP_ID=""
  hs_note "session cleanup identity was not installed — this session will never be auto-reaped"
fi

# The ledger entry was written before this launcher ran and does not know the session
# survives in tmux. Recording the target is what lets a reader reopen it; a failure here
# must never cost the user their session, hence the warning without termination.
if [[ -n "${AGENT_LEDGER_ID:-}" && "$LEDGER_TARGET_PREBOUND" != 1 ]]; then
  node --input-type=module -e \
    'const [mod, id, name, sock, reapIdentity] = process.argv.slice(1);
     const { updateEntry } = await import(mod);
     const patch = { tmuxSession: name, tmuxSocket: sock };
     if (reapIdentity) patch.tmuxReapIdentity = reapIdentity;
     if (!updateEntry(id, patch)) process.exit(1);' \
    "$SELF_DIR/../lib/agent-session-reader.mjs" "$AGENT_LEDGER_ID" "$NAME" "$SOCK" \
    "$INSTALLED_REAP_ID" >/dev/null 2>&1 \
    || hs_note "session ledger could not record the tmux cleanup identity — this session will never be auto-reaped"
fi

detached=0
session_rows="$(tmux_ list-sessions -F '#{session_name} #{session_attached}' 2>/dev/null || :)"
while read -r session_name session_attached; do
  [[ -n "$session_name" && "$session_name" != "$NAME" && "$session_attached" == 0 ]] &&
    detached=$((detached + 1))
done <<<"$session_rows"
(( detached > 0 )) && hs_note "$detached earlier session(s) still running detached — reattach with: claude-sessions"

exec tmux -S "$SOCK" attach-session -t "$NAME"
