#!/usr/bin/env bash
# _tmpjail-shim.sh — shared body for the /tmp-jail PATH shims in this directory.
# Each agent shim is a symlink to this file; the symlink set IS the list of runtimes the
# session ledger covers, so adding a CLI here needs no change in the reader.
#
# Why a PATH shim and not a shell alias: PATH is inherited across every exec
# boundary, so this fires for ANY launch method -- interactive shell, a
# non-interactive `bash -c`, a launcher script, cron, or another agent's
# subprocess. Aliases only exist in the one interactive shell that defined
# them, which is why they never covered scripted agent sessions. This dir
# (~/.claude/bin) is first in PATH, so it wins over the real binaries in
# ~/.npm-global/bin and ~/.local/bin. Agent self-updates repoint those real
# symlinks but never touch this shim, and `real` is re-resolved each run, so
# the jail survives updates.
#
# OD_PATH_SHIM_MARKER — identifies this file to every shim resolver, so a second copy
# of it on PATH is rejected as a candidate instead of being invoked as the "real"
# binary (wrapper invoking itself = unbounded fork loop).
set -e
name="${0##*/}"
SHIM_DIR="$(cd "$(dirname "$(readlink -f -- "${BASH_SOURCE[0]}")")" && pwd)"

GUARD_LIB="$SHIM_DIR/../lib/shim-guard.sh"
if [[ ! -r "$GUARD_LIB" ]]; then
  echo "$name: tmpjail shim cannot read $GUARD_LIB — refusing" >&2
  exit 78
fi
# shellcheck source=../lib/shim-guard.sh
source "$GUARD_LIB"
shim_guard_enter "$name"

real="$(shim_resolve_real "$name" "$SHIM_DIR")" || real=""
if [[ -z "$real" ]]; then
  echo "$name: command not found (tmpjail shim found no real binary outside $SHIM_DIR)" >&2
  exit 127
fi
shim_guard_clear "$name"

if [[ "$SHIM_REENTRY" == 1 ]]; then
  exec "$real" "$@"
fi

# Skip the npm Node launcher when its native Codex payload is available.
if [[ "$name" == "codex" && "$real" == "$HOME/.npm-global/bin/codex" ]]; then
  _codex_package_root="$HOME/.npm-global/lib/node_modules/@openai/codex"
  _codex_platform_package=""
  _codex_target=""
  case "${OSTYPE:-}:${HOSTTYPE:-}" in
    linux*:x86_64)
      _codex_platform_package="codex-linux-x64"
      _codex_target="x86_64-unknown-linux-musl"
      ;;
    linux*:aarch64 | linux*:arm64)
      _codex_platform_package="codex-linux-arm64"
      _codex_target="aarch64-unknown-linux-musl"
      ;;
    darwin*:x86_64)
      _codex_platform_package="codex-darwin-x64"
      _codex_target="x86_64-apple-darwin"
      ;;
    darwin*:aarch64 | darwin*:arm64)
      _codex_platform_package="codex-darwin-arm64"
      _codex_target="aarch64-apple-darwin"
      ;;
  esac
  _codex_native="$_codex_package_root/node_modules/@openai/$_codex_platform_package/vendor/$_codex_target/bin/codex"
  if [[ -n "$_codex_target" && ! -x "$_codex_native" ]]; then
    _codex_native="$_codex_package_root/vendor/$_codex_target/bin/codex"
  fi
  if [[ -n "$_codex_target" && -x "$_codex_native" ]]; then
    real="$_codex_native"
    CODEX_MANAGED_PACKAGE_ROOT="$_codex_package_root"
    CODEX_MANAGED_BY_NPM=1
    unset CODEX_MANAGED_BY_BUN CODEX_MANAGED_BY_PNPM
    export CODEX_MANAGED_PACKAGE_ROOT CODEX_MANAGED_BY_NPM
  fi
fi

if [[ "$name" == "codex" ]]; then
  export CODEX_SQLITE_HOME="${CODEX_SQLITE_HOME:-$HOME/.codex-shared-state}"
fi

# Durable session ledger. The entry reaches disk BEFORE the exec below, so a session
# whose terminal dies is still discoverable afterwards. AGENT_LEDGER_ID is exported into
# the whole tree; readers resolve the live PID by scanning /proc/*/environ for it, which
# is immune to PID reuse. Contract: overdeck docs/agent-session-ledger.md.
#
# Launches are additionally hosted by a systemd-owned tmux server so Overdeck can capture
# their live pane. Interactive launches attach to it; non-interactive and already-jailed
# launches leave it detached while the caller waits for the runtime to finish.
# Kill switch: AGENT_LEDGER_MUX=0.
#
# A human's own session is routed to _human-session (shared tmux in human.slice) instead, so
# it must not also claim a dedicated server nothing will ever create. Classify first, then record.
HUMAN_SESSION_RUNTIMES="${HUMAN_SESSION_RUNTIMES:-claude}"
AGENT_LEDGER_HUMAN=0
AGENT_LEDGER_HUMAN_TARGET=""
# TMPJAIL_ACTIVE is inherited through exec and cannot decide who launched this runtime.
# HUMAN_SESSION_ACTIVE only prevents re-entering _human-session from its own tmux pane.
if [[ -z "${HUMAN_SESSION_ACTIVE:-}" && " $HUMAN_SESSION_RUNTIMES " == *" $name "* ]] &&
  [[ -r "$SHIM_DIR/../lib/session-class.sh" ]]; then
  {
    # shellcheck source=../lib/session-class.sh
    . "$SHIM_DIR/../lib/session-class.sh"
    session_is_human >/dev/null && AGENT_LEDGER_HUMAN=1
  } || :
fi

# tmux refuses to attach a client whose terminfo entry lacks `clear`, and that refusal comes
# after the session has been created: the terminal is left with nothing.
_shim_term_drivable() {
  [[ -n "${TERM:-}" && "$TERM" != dumb ]] || return 1
  command -v tput >/dev/null 2>&1 || return 0
  tput -T "$TERM" clear >/dev/null 2>&1
}

if [[ -r "$HOME/.claude/lib/agent-session-ledger.sh" ]]; then
  {
    # shellcheck source=/dev/null
    . "$HOME/.claude/lib/agent-session-ledger.sh"
    AGENT_LEDGER_MUX_WANT=0
    AGENT_LEDGER_MUX_ATTACH=0
    _ledger_sock=""
    if [[ "$AGENT_LEDGER_HUMAN" != 1 ]] &&
      [[ -z "${TMUX:-}" && "${AGENT_LEDGER_MUX:-1}" != "0" ]]; then
      AGENT_LEDGER_MUX_WANT=1
      if [[ -z "${TMPJAIL_ACTIVE:-}" && -t 0 && -t 1 ]] && _shim_term_drivable; then
        AGENT_LEDGER_MUX_ATTACH=1
      fi
    fi
    _human_tmux_socket=""
    [[ "$AGENT_LEDGER_HUMAN" == 1 ]] && _human_tmux_socket="$HOME/.local/state/human-session/tmux.sock"
    _ledger_id="$(agent_ledger_birth "$name" "$_human_tmux_socket")"
    if [[ -n "$_ledger_id" ]]; then
      export AGENT_LEDGER_ID="$_ledger_id"
      if [[ "$AGENT_LEDGER_HUMAN" == 1 ]]; then
        AGENT_LEDGER_HUMAN_TARGET="$_ledger_id"
        export AGENT_LEDGER_HUMAN_TARGET
      fi
      _ledger_sock="$(agent_ledger_dir)/sock/$_ledger_id"
    else
      AGENT_LEDGER_MUX_WANT=0
    fi
  } || :
fi

# The escape hatch already placed this process in its own unsafe.slice scope, and the session
# cap is an agent-concurrency control — queueing the hatch behind it locks the owner out.
if [[ -n "${UNSAFE_HATCH_ACTIVE:-}" ]]; then
  exec "$real" "$@"
fi

# A human typing this at a terminal owns the session; an agent spawning it does not. The
# human's goes to human.slice (no ceiling, survives its terminal), the agent's keeps today's
# agent.slice confinement. HUMAN_SESSION_ACTIVE marks the inside of a protected session, so a
# runtime launched from within one is confined as the agent it is.
if [[ "${AGENT_LEDGER_HUMAN:-0}" == 1 ]]; then
  exec "$SHIM_DIR/_human-session" "$real" "$@"
fi

# The dedicated tmux server is systemd-owned in agent.slice. The pane still enters through
# _agent-session-admission, so the runtime keeps the session cap, the confine-agent scope
# and its ceilings.
if [[ ${AGENT_LEDGER_MUX_WANT:-0} == 1 && -n "${_ledger_sock:-}" ]]; then
  exec env AGENT_LEDGER_MUX_SOCKET="$_ledger_sock" AGENT_LEDGER_MUX_TARGET=main \
    AGENT_LEDGER_MUX_ATTACH="${AGENT_LEDGER_MUX_ATTACH:-0}" \
    AGENT_LEDGER_MUX_JAILED="$([[ -n "${TMPJAIL_ACTIVE:-}" ]] && printf 1 || printf 0)" \
    "$SHIM_DIR/_agent-session-tmux" "$real" "$@"
fi

# Already inside a jail (nested agent launch): don't wrap again -- nested bwrap can fail,
# and the child inherits the parent's /tmp overlay anyway. This is reached only when mux
# hosting is disabled or the ledger could not allocate the dedicated socket.
if [[ -n "${TMPJAIL_ACTIVE:-}" ]]; then
  exec "$SHIM_DIR/_agent-session-admission" "$real" "$@"
fi

exec "$SHIM_DIR/_agent-session-admission" tmpjail "$real" "$@"
