#!/usr/bin/env bash
# cmux codex wrapper - per-invocation Codex hook injection + launch detection.
#
# When running inside a cmux terminal (CMUX_SURFACE_ID is set), this wrapper
# makes `codex` carry exactly one cmux producer per event. The emitter reads
# ~/.codex/hooks.json without changing it, then injects only missing cmux hooks
# for THIS invocation. It never rewrites the user's Codex settings.
# Codex's own SessionStart/UserPromptSubmit/Stop/PreToolUse/PostToolUse/
# PermissionRequest then fire back into cmux with Codex's real session_id.
# Those hook payloads are the source of truth for session identity; the wrapper
# never predicts fresh-vs-resume state from Codex's command-line syntax.
#
# Outside cmux (no CMUX_SURFACE_ID), or when the user opts out
# (CMUX_CODEX_HOOKS_DISABLED=1), the wrapper does NOTHING but exec the real
# codex. Every failure path below also execs the real codex, so installing the
# wrapper can never break `codex`.

# Deliberately NOT using `set -e`/`set -u`: a wrapper that aborts on any error
# would break the user's `codex`. Each helper handles its own failures and the
# script always reaches an `exec "$REAL_CODEX"`.

# The effective uid, read once from the kernel. Bash's $EUID is not usable here:
# macOS /bin/bash 3.2 takes EUID from the environment, and the uid decides which
# owner the cmux-cua trust check and auth-token check accept. Cleared at startup
# so the environment cannot seed it.
cmux_codex_wrapper_uid=""
cmux_codex_wrapper_effective_uid() {
    [[ -n "$cmux_codex_wrapper_uid" ]] || cmux_codex_wrapper_uid="$(/usr/bin/id -u 2>/dev/null || true)"
    [[ "$cmux_codex_wrapper_uid" =~ ^[0-9]+$ ]]
}

cmux_codex_wrapper_is_self_or_shim() {
    local candidate="$1"
    [[ -n "$candidate" ]] || return 1
    if [[ -e "$candidate" && "$candidate" -ef "$0" ]]; then
        return 0
    fi
    if [[ -n "${CMUX_CODEX_WRAPPER_SHIM:-}" &&
          -e "$candidate" &&
          -e "$CMUX_CODEX_WRAPPER_SHIM" &&
          "$candidate" -ef "$CMUX_CODEX_WRAPPER_SHIM" ]]; then
        return 0
    fi
    if [[ -n "${CMUX_CODEX_WRAPPER_SHIM_ROOT:-}" &&
          "$candidate" == "${CMUX_CODEX_WRAPPER_SHIM_ROOT%/}/codex" ]]; then
        return 0
    fi
    case "$candidate" in
        */cmux-cli-shims/*/codex|*/cmux-cli-shims/codex)
            return 0
            ;;
        */Contents/Resources/bin/codex|*/Resources/bin/codex)
            return 0
            ;;
    esac
    return 1
}

codex_launch_has_subrouter_provider() {
    local argument assignment compact provider=""
    local expects_config_value=0
    for argument in "$@"; do
        [[ "$argument" == "--" ]] && break
        assignment=""
        if (( expects_config_value )); then
            assignment="$argument"
            expects_config_value=0
        else
            case "$argument" in
                -c|--config)
                    expects_config_value=1
                    continue
                    ;;
                -c=*|--config=*)
                    assignment="${argument#*=}"
                    ;;
                *)
                    continue
                    ;;
            esac
        fi
        compact="${assignment//[[:space:]]/}"
        case "$compact" in
            model_provider=subrouter|model_provider=\"subrouter\"|model_provider=\'subrouter\')
                provider="subrouter"
                ;;
            model_provider=*)
                provider="other"
                ;;
        esac
    done
    [[ "$provider" == "subrouter" ]]
}

# Find the real codex binary, skipping this wrapper and cmux's shell shims.
find_real_codex() {
    local custom="${CMUX_CUSTOM_CODEX_PATH:-}"
    custom="${custom#"${custom%%[![:space:]]*}"}"  # trim leading whitespace
    custom="${custom%"${custom##*[![:space:]]}"}"  # trim trailing whitespace
    if [[ -n "$custom" && -f "$custom" && -x "$custom" ]]; then
        if ! cmux_codex_wrapper_is_self_or_shim "$custom"; then
            printf '%s' "$custom"
            return 0
        fi
    fi
    local self_dir
    self_dir="$(cd "$(dirname "$0")" && pwd)"
    local IFS=:
    for d in $PATH; do
        [[ "$d" == "$self_dir" ]] && continue
        local candidate="$d/codex"
        cmux_codex_wrapper_is_self_or_shim "$candidate" && continue
        [[ -x "$candidate" ]] && printf '%s' "$candidate" && return 0
    done
    return 1
}

resolve_hook_cmux_bin() {
    local self_dir bundled_cli
    bundled_cli="${CMUX_BUNDLED_CLI_PATH:-}"
    if [[ -n "$bundled_cli" && -x "$bundled_cli" ]]; then
        printf '%s' "$bundled_cli"
        return 0
    fi

    self_dir="$(cd "$(dirname "$0")" && pwd)"
    bundled_cli="$self_dir/cmux"
    if [[ -x "$bundled_cli" ]]; then
        printf '%s' "$bundled_cli"
        return 0
    fi

    bundled_cli="$(command -v cmux 2>/dev/null || true)"
    if [[ -n "$bundled_cli" ]]; then
        printf '%s' "$bundled_cli"
        return 0
    fi

    printf '%s' "cmux"
}

cmux_codex_load_skill_link_policy() {
    local self_dir resource_dir policy
    self_dir="$(cd "$(dirname "$0")" 2>/dev/null && pwd -P)" || return 1
    resource_dir="${self_dir%/bin}"
    policy="$resource_dir/cmux-cua/link-policy.sh"
    [[ -r "$policy" ]] || return 1
    # The policy is shipped inside the same app resource directory as this
    # wrapper. It is the one mutation path shared with the Claude wrapper.
    # shellcheck source=/dev/null
    . "$policy"
}

cmux_computer_use_surface_socket_available() {
    # CMUX_SURFACE_ID can outlive its owning cmux process in a detached shell.
    # Keep main's hook injection behavior independent of socket reachability,
    # but require a live cmux owner before attaching the TCC-sensitive Computer
    # Use broker to this process chain. Checking the Unix socket node is
    # intentionally process-free: launch instrumentation must not wait on or
    # otherwise depend on a transient cmux ping.
    local socket="${CMUX_SOCKET_PATH:-}"
    [[ -n "$socket" && -S "$socket" ]]
}

cmux_computer_use_json_escape() {
    local value="$1"
    value="${value//\\/\\\\}"
    value="${value//\"/\\\"}"
    value="${value//$'\n'/\\n}"
    value="${value//$'\r'/\\r}"
    value="${value//$'\t'/\\t}"
    printf '%s' "$value"
}

# The installed-helper client selection and its trust checks live in the
# cmux-cua resource beside link-policy.sh, shared with the Claude wrapper.
# Cleared at startup so the environment cannot pre-answer it.
cmux_codex_client_policy_loaded=""
cmux_codex_load_client_policy() {
    [[ "$cmux_codex_client_policy_loaded" == "1" ]] && return 0
    local self_dir resource_dir policy
    self_dir="$(cd "$(dirname "$0")" 2>/dev/null && pwd -P)" || return 1
    resource_dir="${self_dir%/bin}"
    policy="$resource_dir/cmux-cua/client-policy.sh"
    [[ -r "$policy" ]] || return 1
    # shellcheck source=/dev/null
    . "$policy" || return 1
    cmux_codex_client_policy_loaded=1
}

cmux_codex_resolve_computer_use_client() {
    cmux_codex_load_client_policy || return 1
    cmux_computer_use_resolve_client
}

cmux_computer_use_auth_token() {
    local auth_file metadata owner mode token
    auth_file="${CMUX_CUA_AUTH_TOKEN_FILE:-}"
    if [[ -n "$auth_file" && "$auth_file" == /* && -f "$auth_file" && ! -L "$auth_file" ]]; then
        metadata="$(/usr/bin/stat -f '%u %Lp' "$auth_file" 2>/dev/null || true)"
        read -r owner mode <<< "$metadata"
        if cmux_codex_wrapper_effective_uid && [[ "$owner" == "$cmux_codex_wrapper_uid" && "$mode" == "600" ]]; then
            IFS= read -r token < "$auth_file" || true
            if [[ -n "$token" ]]; then
                printf '%s' "$token"
                return 0
            fi
        fi
    fi
    token="${CMUX_CUA_SOCKET_AUTH_TOKEN:-}"
    [[ -n "$token" ]] || return 1
    printf '%s' "$token"
}

cmux_codex_global_skill_install_enabled() {
    # Codex's picker scans filesystem skill roots before it starts a session.
    # Writing a user-global link is therefore an explicit opt-in, never a
    # side effect of starting an agent.
    case "${CMUX_COMPUTER_USE_INSTALL_GLOBAL_SKILL:-0}" in
        1|true|TRUE|yes|YES) return 0 ;;
    esac
    return 1
}

cmux_codex_reconcile_skill_links() {
    local self_dir resource_dir source_dir install_requested=0
    self_dir="$(cd "$(dirname "$0")" 2>/dev/null && pwd -P)" || return 1
    resource_dir="${self_dir%/bin}"
    source_dir="$resource_dir/cmux-cua"
    [[ -f "$source_dir/SKILL.md" ]] || return 1
    cmux_codex_global_skill_install_enabled && install_requested=1

    if ! cmux_codex_load_skill_link_policy; then
        # An older app may not carry the policy helper. Fail closed rather than
        # mutating an unknown global path or creating a duplicate picker row.
        return 0
    fi

    cmux_cua_skill_reconcile codex "$source_dir" "${PWD:-}" "$install_requested" || return 1
}

cmux_codex_computer_use_app_enabled() {
    # The shell shim normally refreshes this authority before every invocation,
    # but the wrapper can also be launched directly (or through an older shim).
    # Re-read the app-owned setting here so a live disable cannot leave Codex
    # attached through stale spawn-time environment.
    local enabled="${CMUX_COMPUTER_USE_APP_ENABLED:-1}"
    local home="${HOME:-}"
    if [[ "$home" = /* ]]; then
        local setting_file="$home/Library/Application Support/cmux/cmux-cua/enabled"
        local legacy_setting_file="$home/Library/Application Support/cmux/computer-use/enabled"
        if [[ ! -r "$setting_file" && -r "$legacy_setting_file" ]]; then
            setting_file="$legacy_setting_file"
        fi
        if [[ -r "$setting_file" ]]; then
            IFS= read -r enabled < "$setting_file" || true
        fi
    fi
    [[ "$enabled" != "0" ]]
}

# Attachment diagnostics are deliberately opt-in. The normal wrapper remains
# quiet, while a maintainer can set CMUX_CUA_DIAGNOSTICS=1 to see the single
# fail-closed reason for this invocation. Keep this machine-readable line
# free of paths, socket names, and credentials: it is safe to collect from a
# terminal transcript without weakening the TCC/proxy boundary.
cmux_codex_cua_diagnostic() {
    [[ "${CMUX_CUA_DIAGNOSTICS:-0}" == "1" ]] || return 0
    local reason="${1:-unknown}"
    printf 'cmux-cua: codex attachment=%s\n' "$reason" >&2
}

cmux_codex_cua_attachment_present() {
    local arg
    for arg in "${cmux_codex_computer_use_args[@]}"; do
        [[ "$arg" == mcp_servers.cmux-cua.command=* ]] && return 0
    done
    return 1
}

# Re-evaluate the same fail-closed gates in the parent shell after the
# NUL-separated emitter finishes. The emitter runs in process substitution,
# so shell variables assigned there cannot be used as a diagnostic channel.
# These checks are read-only (including the private credential validation) and
# do not launch a helper or probe the daemon.
cmux_codex_cua_attachment_reason() {
    if ! cmux_codex_computer_use_enabled_for_invocation "$@"; then
        if [[ "${CMUX_COMPUTER_USE_MCP_DISABLED:-0}" == "1" ]]; then
            printf '%s' 'computer-use-disabled'
        else
            printf '%s' 'strict-mcp-config'
        fi
        return 0
    fi
    if ! cmux_computer_use_surface_socket_available; then
        printf '%s' 'stale-cmux-socket'
        return 0
    fi
    if ! cmux_codex_resolve_computer_use_client >/dev/null 2>&1; then
        printf '%s' 'missing-client'
        return 0
    fi
    if ! cmux_computer_use_auth_token >/dev/null 2>&1; then
        printf '%s' 'missing-auth'
        return 0
    fi
    printf '%s' 'attachment-unavailable'
}

# Local/tagged builds often run Codex through an ad-hoc-signed wrapper (for
# example the developer's `sr` launcher), so the helper cannot verify the
# production OpenAI parent requirement. Keep the helper's escape hatch scoped
# to the two identity values baked into a reload.sh tagged app; stable builds
# retain the signed-parent gate.
cmux_codex_is_tagged_dev_build() {
    [[ -n "${CMUX_TAG:-}" && "${CMUX_BUNDLE_ID:-}" == com.cmuxterm.app.debug.* ]]
}

cmux_codex_computer_use_enabled_for_invocation() {
    [[ "${CMUX_COMPUTER_USE_MCP_DISABLED:-0}" == "1" ]] && return 1
    local arg
    for arg in "$@"; do
        case "$arg" in
            --strict-mcp-config|--strict-mcp-config=*)
                return 1
                ;;
        esac
    done
    return 0
}

cmux_codex_emit_computer_use_args() {
    cmux_codex_computer_use_enabled_for_invocation "$@" || return 1
    # Codex v0.153 consumes skills.config as enable/disable rules only; it does
    # not turn a session flag path into a discovery root. A picker-visible
    # bundled skill therefore requires the explicit global-install opt-in
    # reconciled above. The MCP attachment remains fail-closed on cmux
    # ownership, daemon credentials, and the installed approval broker.
    cmux_computer_use_surface_socket_available || return 0
    local client default_session runtime_scope runtime_uid cua_socket state_dir
    # Load the client policy and read the uids in this shell first: the
    # helpers below run in command substitutions, so anything they load or
    # cache would not survive back here.
    cmux_codex_wrapper_effective_uid || return 1
    cmux_codex_load_client_policy || return 1
    cmux_cua_client_effective_uid || return 1
    client="$(cmux_computer_use_resolve_client)" || return 1
    # Gate on the credential here; the parent shell exports its value.
    cmux_computer_use_auth_token >/dev/null || return 1
    # Stable per-terminal session id so the branded cursor survives helper restarts.
    default_session="cmux-${CMUX_SURFACE_ID:-$$}"
    runtime_scope="${CMUX_CUA_RUNTIME_SCOPE:-${CMUX_TAG:-default}}"
    # The common scope (a bundle id or tag) is already a clean slug; only
    # sanitize through the three-process pipeline when it is not.
    if [[ ! "$runtime_scope" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,62}[A-Za-z0-9_]$ ]]; then
        runtime_scope="$(printf '%s' "$runtime_scope" | LC_ALL=C /usr/bin/tr -c 'A-Za-z0-9_.-' '-' | /usr/bin/sed -E 's/^[.-]+//; s/[.-]+$//' | /usr/bin/cut -c1-64)"
    fi
    [[ -n "$runtime_scope" ]] || runtime_scope="default"
    runtime_uid="$cmux_codex_wrapper_uid"
    cua_socket="${CMUX_CUA_CODEX_SOCKET_PATH:-/tmp/cmux-cua-${runtime_uid}/${runtime_scope}/cmux-cua-codex.sock}"
    state_dir="${CMUX_CUA_STATE_DIR:-${HOME}/Library/Application Support/cmux/cmux-cua/runtime/${runtime_scope}/state}"
    # The helper owns external onboarding. Mark that contract explicitly so
    # protected calls also wait for cmux's post-verification readiness signal.
    printf '%s\0' \
        "-c" \
        "mcp_servers.cmux-cua.command=\"$(cmux_computer_use_json_escape "$client")\"" \
        "-c" \
        "mcp_servers.cmux-cua.args=[\"mcp\",\"--socket\",\"$(cmux_computer_use_json_escape "$cua_socket")\",\"--codex-computer-use-compat\"]" \
        "-c" \
        'mcp_servers.cmux-cua.env.CMUX_CUA_MCP_FORCE_PROXY="1"' \
        "-c" \
        'mcp_servers.cmux-cua.env.CMUX_CUA_EXTERNAL_PERMISSION_FLOW="1"' \
        "-c" \
        'mcp_servers.cmux-cua.env_vars=["CMUX_CUA_SOCKET_AUTH_TOKEN"]' \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_DEFAULT_SESSION=\"$(cmux_computer_use_json_escape "$default_session")\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_STATE_OWNER_PID=\"$(cmux_computer_use_json_escape "${CMUX_CODEX_PID:-$$}")\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_TELEMETRY_ENABLED=\"false\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_UPDATE_CHECK=\"false\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_CURSOR_GRADIENT=\"#12c7f5,#2d8cff,#6c5cff\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_CURSOR_BLOOM=\"#2d8cff\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_CURSOR_LABEL=\"cmux\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.CMUX_CUA_STATE_DIR=\"$(cmux_computer_use_json_escape "$state_dir")\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.NODE_OPTIONS=\"\"" \
        "-c" \
        "mcp_servers.cmux-cua.env.BUN_OPTIONS=\"\""

    if cmux_codex_is_tagged_dev_build; then
        printf '%s\0' \
            "-c" \
            'mcp_servers.cmux-cua.env.CMUX_CUA_CODEX_ALLOW_UNVERIFIED_CLIENT="1"'
    fi
}

# The interactive session, `exec`/`e`, `resume`, and `fork` entrypoints all START
# a Codex session, so all four must receive cmux's hook injection; everything
# else (review, login, mcp, doctor, --help, --version, ...) passes through
# untouched. `resume` is critical: cmux auto-resumes a session as `codex resume
# <id>` after a Mac relaunch, and without hooks the resumed process fires no
# SessionStart, so the registry keeps only the stale pre-relaunch record (its
# old pid is dead -> seeded `.ended`) and the iOS GUI shows it read-only with no
# input bar. Injecting hooks on resume lets the live resumed session re-bind and
# flip back to idle/editable. codex accepts the global `--enable hooks` /
# `--dangerously-bypass-hook-trust` / `-c hooks.X=...` flags before the `resume`
# subcommand, so prepending them is safe. Mirrors should_inject_claude_hooks: a
# leading non-option token that is a known Codex subcommand means "not a session"
# unless it is exec, e, resume, or fork.
codex_subcommand_starts_session() {
    # `fork` duplicates a previous interactive session into a new live one:
    # it takes an optional prompt and accepts the same global flags before
    # the subcommand as `resume` (verified against codex-cli 0.144), so it
    # needs hook and computer-use injection exactly like exec/resume.
    case "$1" in
        exec|e|resume|fork) return 0 ;;
        *) return 1 ;;
    esac
}

codex_known_subcommand() {
    case "$1" in
        exec|e|review|login|logout|mcp|plugin|mcp-server|app-server|\
        remote-control|app|completion|update|doctor|sandbox|debug|apply|a|\
        resume|archive|delete|unarchive|fork|cloud|exec-server|features|help)
            return 0
            ;;
    esac
    return 1
}

# Options that take a value, so a following non-option token is the value and
# not the subcommand. Conservative: unknown `--flag value` is handled by the
# "first bare token decides" rule below, which is safe because Codex session
# launches take an optional prompt, not a subcommand.
codex_option_consumes_value() {
    case "$1" in
        -c|--config|-m|--model|-p|--profile|-C|--cd|--remote|-a|--ask-for-approval|\
        -s|--sandbox|--output-last-message|--enable|--disable|--add-dir|-i|--image|\
        --local-provider|--remote-auth-token-env|--output-schema|--color)
            return 0
            ;;
    esac
    return 1
}

codex_passthrough_option_flag() {
    case "$1" in
        --help|-h|-V|--version)
            return 0
            ;;
    esac
    return 1
}

# Decide whether this invocation is a Codex SESSION entrypoint we should inject
# hooks into. Bare `codex` (no args) and `codex [prompt]` are interactive
# sessions; `codex exec ...` is a non-interactive session; `codex resume ...`
# (picker, <id>, --last, --all <id>) resumes a session; `codex fork ...` starts
# a child session whose hooks report the new id; any other leading subcommand
# is not.
should_inject_codex_hooks() {
    (( $# == 0 )) && return 0

    local arg
    local skip_next=false
    for arg in "$@"; do
        if [[ "$skip_next" == true ]]; then
            skip_next=false
            continue
        fi
        case "$arg" in
            --)
                return 0
                ;;
            -*)
                if codex_passthrough_option_flag "$arg"; then
                    return 1
                fi
                if [[ "$arg" != *=* ]] && codex_option_consumes_value "$arg"; then
                    skip_next=true
                fi
                continue
                ;;
            *)
                if codex_known_subcommand "$arg"; then
                    codex_subcommand_starts_session "$arg" && return 0
                    return 1
                fi
                # First bare token is a prompt -> interactive session.
                return 0
                ;;
        esac
    done

    return 0
}

# Codex declares -c/--config, --enable, and --disable as clap `global`
# arguments. When a subcommand also receives one of them, Codex keeps only the
# subcommand-level values: on codex-cli 0.159.3, `codex -c a=1 mcp -c b=2 list`
# sees only b. The wrapper places its own overrides before the user's argv, so
# `codex exec -c model=x` or `codex resume --last -c ...` silently dropped the
# cmux-cua MCP server, the hook config, and `--disable computer_use`. A global
# argument means the same thing at either position, so move the user's
# subcommand-level occurrences, in order, in front of the subcommand. Codex then
# sees one root-level list: wrapper values, user root values, user subcommand
# values, with later values winning as before. Tokens after `--` are prompt
# text and stay in place. Without a subcommand nothing moves.
cmux_codex_session_args=()
cmux_codex_hoist_session_global_args() {
    local -a head=() hoisted=() tail=()
    local arg
    cmux_codex_session_args=()
    while (( $# > 0 )); do
        arg="$1"
        case "$arg" in
            --)
                head+=("$@")
                break
                ;;
            -*)
                head+=("$arg")
                shift
                if [[ "$arg" != *=* ]] && codex_option_consumes_value "$arg" && (( $# > 0 )); then
                    head+=("$1")
                    shift
                fi
                continue
                ;;
        esac
        if ! codex_known_subcommand "$arg"; then
            # First bare token is a prompt: an interactive session has no
            # subcommand scope, so every option is already root-level.
            head+=("$@")
            break
        fi
        tail+=("$arg")
        shift
        while (( $# > 0 )); do
            arg="$1"
            case "$arg" in
                --)
                    tail+=("$@")
                    break
                    ;;
                -c|--config|--enable|--disable)
                    if (( $# >= 2 )); then
                        hoisted+=("$arg" "$2")
                        shift 2
                    else
                        tail+=("$arg")
                        shift
                    fi
                    ;;
                -c=*|--config=*|--enable=*|--disable=*|-c?*)
                    hoisted+=("$arg")
                    shift
                    ;;
                -*)
                    tail+=("$arg")
                    shift
                    if [[ "$arg" != *=* ]] && codex_option_consumes_value "$arg" && (( $# > 0 )); then
                        tail+=("$1")
                        shift
                    fi
                    ;;
                *)
                    tail+=("$arg")
                    shift
                    ;;
            esac
        done
        break
    done
    (( ${#head[@]} > 0 )) && cmux_codex_session_args+=("${head[@]}")
    (( ${#hoisted[@]} > 0 )) && cmux_codex_session_args+=("${hoisted[@]}")
    (( ${#tail[@]} > 0 )) && cmux_codex_session_args+=("${tail[@]}")
    return 0
}

# `codex exec` runs headless. It shares the pane's surface with any
# interactive Codex there, so the agent message hooks skip it.
codex_launch_is_headless() {
    local arg
    local skip_next=false
    for arg in "$@"; do
        if [[ "$skip_next" == true ]]; then
            skip_next=false
            continue
        fi
        case "$arg" in
            --) return 1 ;;
            -*)
                if [[ "$arg" != *=* ]] && codex_option_consumes_value "$arg"; then
                    skip_next=true
                fi
                continue
                ;;
            exec|e) return 0 ;;
            *) return 1 ;;
        esac
    done
    return 1
}

# Codex does not emit its own SessionStart hook for an explicit `resume <id>`
# launch. The wrapper is the only layer that knows both the resumed session id
# and the new live process, so replay that lifecycle event after exporting the
# launch identity below. Picker/`--last` resumes have no id at exec time and
# remain dependent on Codex's own lifecycle event once it selects a session.
cmux_codex_resume_session_id() {
    local seen_resume=false arg
    for arg in "$@"; do
        if [[ "$seen_resume" == true ]]; then
            if [[ "$arg" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
                printf '%s' "$arg"
                return 0
            fi
        elif [[ "$arg" == "resume" ]]; then
            seen_resume=true
        fi
    done
    return 1
}

# A direct `codex fork` creates its child rollout before the first prompt but
# does not reliably emit SessionStart at that point. The app marks cmux-owned
# fork launches with the parent id; keep a bounded monitor alive beside Codex
# so the rollout's forked_from_id can become the destination pane's identity.
cmux_codex_start_fork_session_watch() {
    local parent_session_id="${CMUX_AGENT_FORK_PARENT_SESSION_ID:-}"
    [[ -n "$parent_session_id" ]] || return 0
    [[ -n "${CMUX_CODEX_HOOK_CMUX_BIN:-}" && -x "$CMUX_CODEX_HOOK_CMUX_BIN" ]] || return 0
    local launch_at="${CMUX_AGENT_FORK_LAUNCH_AT:-}"
    local launch_id="${CMUX_AGENT_FORK_LAUNCH_ID:-}"
    local owner_pid="${CMUX_CODEX_PID:-}"
    local -a monitor_args=(hooks codex monitor --fork-parent "$parent_session_id")
    [[ -n "$launch_at" ]] && monitor_args+=(--fork-launch-at "$launch_at")
    [[ -n "$launch_id" ]] && monitor_args+=(--fork-launch-id "$launch_id")
    [[ -n "$owner_pid" ]] && monitor_args+=(--fork-owner-pid "$owner_pid")
    if [[ -n "${CMUX_SOCKET_PATH:-}" ]]; then
        nohup "$CMUX_CODEX_HOOK_CMUX_BIN" --socket "$CMUX_SOCKET_PATH" "${monitor_args[@]}" \
            </dev/null >/dev/null 2>&1 &
    else
        nohup "$CMUX_CODEX_HOOK_CMUX_BIN" "${monitor_args[@]}" \
            </dev/null >/dev/null 2>&1 &
    fi
}

IN_CMUX=0
if [[ -n "${CMUX_SURFACE_ID:-}" ]]; then
    IN_CMUX=1
fi

# Consume obsolete one-shot launch markers so inherited state never affects the
# hook payloads. Session identity comes from Codex itself, not these markers.
unset CMUX_AGENT_RESTORE_LAUNCH
unset CMUX_AGENT_RESUME_LAUNCH

# Resolve real codex up front; if it cannot be found we error like a missing
# binary would (same as the claude wrapper) rather than silently succeeding.
REAL_CODEX="$(find_real_codex)" || { echo "Error: codex not found in PATH" >&2; exit 127; }

exec_real_codex_passthrough() {
    if [[ "$IN_CMUX" == "1" ]]; then
        local cmux_key
        for cmux_key in "${!CMUX_@}"; do
            unset "$cmux_key"
        done
        unset TERMINFO
    fi
    exec "$REAL_CODEX" "$@"
}

cmux_codex_computer_use_args=()
cmux_codex_read_computer_use_args() {
    local arg auth_token
    while IFS= read -r -d '' arg; do
        cmux_codex_computer_use_args+=("$arg")
    done < <(cmux_codex_emit_computer_use_args "$@" 2>/dev/null)
    (( ${#cmux_codex_computer_use_args[@]} > 0 )) || return 1
    # Codex's -c overrides are argv, and any local user can read argv with ps.
    # Keep the socket credential in the Codex process environment instead; the
    # emitted `env_vars` entry tells Codex to forward it by name to the MCP
    # server. Codex's default shell_environment_policy excludes *TOKEN* names
    # from agent shell commands. The emitter runs in a process substitution, so
    # the export must happen here in the parent shell. Fail closed: without a
    # credential, attach nothing.
    if ! cmux_codex_wrapper_effective_uid ||
       ! auth_token="$(cmux_computer_use_auth_token)"; then
        cmux_codex_computer_use_args=()
        return 1
    fi
    export CMUX_CUA_SOCKET_AUTH_TOKEN="$auth_token"
    return 0
}

# CMUX_CODEX_HOOKS_DISABLED is the documented MASTER opt-out: the wrapper
# stays fully inert — no cmux calls, no Computer Use attach, no argv changes.
if [[ "${CMUX_CODEX_HOOKS_DISABLED:-}" == "1" ]]; then
    cmux_codex_cua_diagnostic "hooks-disabled"
    exec_real_codex_passthrough "$@"
fi

# Outside cmux the wrapper does nothing: Computer Use is a feature of
# cmux-launched agents, so plain `codex` in a random terminal stays untouched.
if [[ "$IN_CMUX" == "0" ]]; then
    cmux_codex_cua_diagnostic "outside-cmux"
    exec_real_codex_passthrough "$@"
fi

# Not a session entrypoint (review/login/doctor/--help/...): pass through with
# no injection of any kind.
if ! should_inject_codex_hooks "$@"; then
    cmux_codex_cua_diagnostic "not-session"
    exec_real_codex_passthrough "$@"
fi

# cmux owns the Computer Use provider for every Codex session it launches.
# Codex also exposes a native `computer` tool behind the `computer_use` feature;
# leaving that feature enabled lets the model silently bypass the explicitly
# requested `cmux-cua` MCP server. Disable the competing provider before any
# session arguments are forwarded. If cmux-cua cannot attach, the session has
# no Computer Use provider rather than falling back to Codex Computer Use.
# This policy is intentionally scoped to cmux-launched sessions. Plain Codex
# outside cmux keeps its normal native Computer Use behavior.
cmux_codex_native_computer_use_args=(--disable computer_use)

# Export launch identity so the hook subprocesses and cmux's agent-pid /
# ended-detection can bind this Codex to its surface and pid. Because we exec
# codex below, $$ becomes the real codex pid. The invocation token is owned by
# this wrapper generation. A wrapper launched from another Codex keeps the
# inherited token as an explicit parent token, then gets a fresh child token;
# a direct `codex exec` that bypasses this wrapper retains the outer token and
# is distinguished by its live hook-process identity.
CMUX_CODEX_PARENT_INVOCATION_ID="${CMUX_CODEX_INVOCATION_ID:-}"
# A hook callback records its own immediate Codex parent. Never let a wrapper
# launched from inside a callback inherit that callback's observation as if it
# were the new process's identity.
unset CMUX_CODEX_HOOK_PID
if command -v uuidgen >/dev/null 2>&1; then
    CMUX_CODEX_INVOCATION_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
else
    CMUX_CODEX_INVOCATION_ID="cmux-codex-$$-$(date +%s)"
fi
export CMUX_CODEX_INVOCATION_ID
if [[ -n "$CMUX_CODEX_PARENT_INVOCATION_ID" ]]; then
    export CMUX_CODEX_PARENT_INVOCATION_ID
else
    unset CMUX_CODEX_PARENT_INVOCATION_ID
fi
export CMUX_CODEX_PID=$$
if codex_launch_is_headless "$@"; then
    export CMUX_CODEX_HEADLESS=1
else
    export CMUX_CODEX_HEADLESS=0
fi
export CMUX_CODEX_HOOK_CMUX_BIN="$(resolve_hook_cmux_bin)"
export CMUX_AGENT_LAUNCH_KIND="codex"
export CMUX_AGENT_LAUNCH_EXECUTABLE="$REAL_CODEX"
export CMUX_AGENT_LAUNCH_CWD="$PWD"
unset CMUX_AGENT_LAUNCH_SUBROUTER_CODEX_RESUME_COMMAND
if codex_launch_has_subrouter_provider "$@"; then
    case "${SUBROUTER_CODEX_RESUME_COMMAND:-}" in
        "sr codex resume"|"subrouter codex resume"|"cx codex resume")
            export CMUX_AGENT_LAUNCH_SUBROUTER_CODEX_RESUME_COMMAND="$SUBROUTER_CODEX_RESUME_COMMAND"
            ;;
    esac
fi
{
    cmux_codex_argv_b64="$(
        {
            printf '%s\0' "$REAL_CODEX"
            if (( $# > 0 )); then
                printf '%s\0' "$@"
            fi
        } | base64 | tr -d '\n'
    )"
    [[ -n "$cmux_codex_argv_b64" ]] && export CMUX_AGENT_LAUNCH_ARGV_B64="$cmux_codex_argv_b64"
}

cmux_codex_start_fork_session_watch
# The monitor received a private copy of these launch markers in its argv.
# Do not let a nested Codex wrapper inherit the parent fork transaction.
unset CMUX_AGENT_FORK_PARENT_SESSION_ID
unset CMUX_AGENT_FORK_LAUNCH_AT
unset CMUX_AGENT_FORK_LAUNCH_ID

# Fresh launches deliberately wait for Codex's real SessionStart payload so no
# fallback identity is created. Explicit resumes do not emit that event, so
# enqueue one with the known session id and current pid. The app's Codex hook
# admission is idempotent for the same session and updates the persisted binding
# to this generation's process; failures are best-effort and never block Codex.
cmux_codex_resume_sid="$(cmux_codex_resume_session_id "$@")"
if [[ -n "$cmux_codex_resume_sid" \
      && -n "$CMUX_CODEX_HOOK_CMUX_BIN" && -x "$CMUX_CODEX_HOOK_CMUX_BIN" ]]; then
    cmux_codex_resume_json_session_id="$(cmux_computer_use_json_escape "$cmux_codex_resume_sid")"
    cmux_codex_resume_json_cwd="$(cmux_computer_use_json_escape "$PWD")"
    cmux_codex_resume_payload="{\"session_id\":\"$cmux_codex_resume_json_session_id\",\"cwd\":\"$cmux_codex_resume_json_cwd\"}"
    if [[ -n "${CMUX_SOCKET_PATH:-}" ]]; then
        printf '%s' "$cmux_codex_resume_payload" \
            | CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC=1 "$CMUX_CODEX_HOOK_CMUX_BIN" \
                --socket "$CMUX_SOCKET_PATH" hooks enqueue codex session-start >/dev/null 2>&1 || true
    else
        printf '%s' "$cmux_codex_resume_payload" \
            | CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC=1 "$CMUX_CODEX_HOOK_CMUX_BIN" \
                hooks enqueue codex session-start >/dev/null 2>&1 || true
    fi
fi

# Build the per-invocation [hooks] injection. The cmux CLI emits the exact arg
# list (NUL-separated). Non-decision commands perform a bounded foreground
# admission to cmux's app-owned ordered queue, so Codex never waits for the
# downstream app socket work. Decision commands remain synchronous so Codex can
# consume their stdout and exit status.
#
# Resolving the args via the CLI (instead of re-deriving fragile shell here)
# avoids quoting bugs in the generated commands. If the emit fails or yields nothing, we
# fall back to plain passthrough so installing the wrapper can never break codex.
CMUX_INJECT_CLI="$CMUX_CODEX_HOOK_CMUX_BIN"
cmux_codex_injected_args=()
cmux_codex_read_inject_args() {
    [[ -n "$CMUX_INJECT_CLI" && -x "$CMUX_INJECT_CLI" ]] || return 1
    local arg
    # Read the NUL-TERMINATED arg stream DIRECTLY via process substitution. A
    # `raw="$(... )"` command substitution must NOT be used: bash discards every
    # NUL byte from command-substitution output (with a warning), which would
    # collapse the whole stream into one token and silently drop the injection.
    if [[ -n "${CMUX_SOCKET_PATH:-}" ]]; then
        while IFS= read -r -d '' arg; do
            cmux_codex_injected_args+=("$arg")
        done < <("$CMUX_INJECT_CLI" --socket "$CMUX_SOCKET_PATH" hooks codex inject-args 2>/dev/null)
    else
        while IFS= read -r -d '' arg; do
            cmux_codex_injected_args+=("$arg")
        done < <("$CMUX_INJECT_CLI" hooks codex inject-args 2>/dev/null)
    fi
    (( ${#cmux_codex_injected_args[@]} > 0 )) || return 1
    return 0
}

# Reconcile the bundled skill's discovery path before the session starts.
# Normal launches clean up only links proven to be cmux-managed and use a
# session-scoped path when it cannot create a same-name picker collision.
# CMUX_COMPUTER_USE_INSTALL_GLOBAL_SKILL=1 is the explicit opt-in for a durable
# user-global link.
if cmux_codex_computer_use_enabled_for_invocation "$@"; then
    # The policy helper performs both explicit installation and stale-link
    # migration; it re-reads the opt-in flag itself. Disabled and strict
    # invocations remain fully inert.
    cmux_codex_reconcile_skill_links || true
fi

# Computer-use args are generated independently of hook injection: the bundled
# CLI is local and does not need the cmux hook socket, so a failed or empty
# `hooks codex inject-args` emit must not silently drop computer use.
cmux_codex_read_computer_use_args "$@" || true

if [[ "${CMUX_CUA_DIAGNOSTICS:-0}" == "1" ]]; then
    if cmux_codex_cua_attachment_present; then
        cmux_codex_cua_diagnostic "attached"
    else
        cmux_codex_cua_reason="$(cmux_codex_cua_attachment_reason "$@")"
        cmux_codex_cua_diagnostic "$cmux_codex_cua_reason"
    fi
fi

cmux_codex_hoist_session_global_args "$@"

if cmux_codex_read_inject_args; then
    exec "$REAL_CODEX" \
        "${cmux_codex_native_computer_use_args[@]}" \
        "${cmux_codex_computer_use_args[@]}" \
        "${cmux_codex_injected_args[@]}" "${cmux_codex_session_args[@]}"
fi

# A reconciled persistent channel intentionally emits no overrides; an emitter
# failure also returns nothing. In both cases preserve the cmux surface context
# and Computer Use attachment so persistent, native, or future hooks can still
# bind this session.
exec "$REAL_CODEX" \
    "${cmux_codex_native_computer_use_args[@]}" \
    "${cmux_codex_computer_use_args[@]}" "${cmux_codex_session_args[@]}"
