resolve_script_dir() {
    local source="${BASH_SOURCE[0]}"
    local dir

    while [ -L "$source" ]; do
        dir="$(cd -P "$(dirname "$source")" && pwd)"
        source="$(readlink "$source")"
        case "$source" in
            /*) ;;
            *) source="$dir/$source" ;;
        esac
    done

    cd -P "$(dirname "$source")" && pwd
}

SCRIPT_DIR="$(resolve_script_dir)"
if [ -z "${CODEX_HOME:-}" ]; then
    if [ -n "${HOME:-}" ]; then
        CODEX_HOME="$HOME/.codex"
    else
        CODEX_HOME=""
    fi
fi
WEBVIEW_DIR="$SCRIPT_DIR/content/webview"
LOG_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/$CODEX_LINUX_APP_ID"
LOG_FILE="$LOG_DIR/launcher.log"
APP_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/$CODEX_LINUX_APP_ID"
APP_SETTINGS_FILE="$APP_CONFIG_DIR/settings.json"
USER_ELECTRON_FLAGS_FILE="$APP_CONFIG_DIR/electron-flags.conf"
CODEX_LINUX_SETTINGS_FILE="$APP_SETTINGS_FILE"
CODEX_LINUX_FEATURES_DIR="$SCRIPT_DIR/.codex-linux/features"
export CODEX_HOME CODEX_LINUX_APP_ID CODEX_LINUX_APP_DISPLAY_NAME CODEX_LINUX_WEBVIEW_PORT CODEX_LINUX_SETTINGS_FILE CODEX_LINUX_FEATURES_DIR
APP_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/$CODEX_LINUX_APP_ID"
APP_PID_FILE="$APP_STATE_DIR/app.pid"
WEBVIEW_PID_FILE="$APP_STATE_DIR/webview.pid"
LAUNCH_ACTION_RUNTIME_DIR="${XDG_RUNTIME_DIR:-$APP_STATE_DIR}/$CODEX_LINUX_APP_ID"
LAUNCH_ACTION_SOCKET="$LAUNCH_ACTION_RUNTIME_DIR/launch-action.sock"
PACKAGED_RUNTIME_HELPER="$SCRIPT_DIR/.codex-linux/codex-packaged-runtime.sh"
COLD_START_HOOK_DIR="$SCRIPT_DIR/.codex-linux/cold-start.d"
FEATURE_ENV_DIR="$SCRIPT_DIR/.codex-linux/env.d"
FEATURE_PRELAUNCH_HOOK_DIR="$SCRIPT_DIR/.codex-linux/prelaunch.d"
FEATURE_ELECTRON_ARGS_DIR="$SCRIPT_DIR/.codex-linux/electron-args.d"
FEATURE_AFTER_EXIT_HOOK_DIR="$SCRIPT_DIR/.codex-linux/after-exit.d"
MANAGED_NODE_BIN_DIR="$SCRIPT_DIR/resources/node-runtime/bin"
APP_NOTIFICATION_ICON_NAME="$CODEX_LINUX_APP_ID"
APP_NOTIFICATION_ICON_BUNDLE="$SCRIPT_DIR/.codex-linux/$APP_NOTIFICATION_ICON_NAME.png"
APP_NOTIFICATION_ICON_SYSTEM="/usr/share/icons/hicolor/256x256/apps/$APP_NOTIFICATION_ICON_NAME.png"
APP_NOTIFICATION_ICON_REPO="$SCRIPT_DIR/../assets/codex.png"
MULTI_LAUNCH_REQUESTED=0
MULTI_LAUNCH_ACTIVE=0
CODEX_LINUX_INSTANCE_ID=""
LAUNCHER_ARGS=()

# Internal marker consumed by patched Electron bundles; never trust inheritance.
unset CODEX_LINUX_MULTI_LAUNCH

early_truthy_env_value() {
    case "${1:-}" in
        1|true|TRUE|yes|YES|on|ON) return 0 ;;
        *) return 1 ;;
    esac
}

normalize_tcp_port() {
    local name="$1"
    local port="$2"
    local value

    case "$port" in
        ""|*[!0-9]*)
            echo "$name must be a TCP port number" >&2
            return 1
            ;;
    esac
    value="$port"
    while [ "${value#0}" != "$value" ]; do
        value="${value#0}"
    done
    [ -n "$value" ] || value=0

    if [ "${#value}" -gt 5 ] || [ "$value" -lt 1 ] || [ "$value" -gt 65535 ]; then
        echo "$name must be between 1 and 65535" >&2
        return 1
    fi

    printf '%s\n' "$value"
}

validate_tcp_port() {
    normalize_tcp_port "$1" "$2" >/dev/null
}

if ! CODEX_LINUX_WEBVIEW_PORT="$(normalize_tcp_port "CODEX_WEBVIEW_PORT" "$CODEX_LINUX_WEBVIEW_PORT")"; then
    exit 1
fi

launcher_port_is_open() {
    local port="$1"
    ( exec 3<>/dev/tcp/127.0.0.1/"$port" ) 2>/dev/null
}

choose_multi_launch_port() {
    local range="${CODEX_MULTI_LAUNCH_PORT_RANGE:-$CODEX_LINUX_WEBVIEW_PORT-$((CODEX_LINUX_WEBVIEW_PORT + 4))}"
    local start end port

    case "$range" in
        *-*)
            start="${range%-*}"
            end="${range#*-}"
            ;;
        *)
            start="$range"
            end="$range"
            ;;
    esac

    start="$(normalize_tcp_port "CODEX_MULTI_LAUNCH_PORT_RANGE start" "$start")" || return 1
    end="$(normalize_tcp_port "CODEX_MULTI_LAUNCH_PORT_RANGE end" "$end")" || return 1
    if [ "$start" -gt "$end" ]; then
        echo "CODEX_MULTI_LAUNCH_PORT_RANGE start must be <= end" >&2
        return 1
    fi

    for port in $(seq "$start" "$end"); do
        if ! launcher_port_is_open "$port"; then
            echo "$port"
            return 0
        fi
    done

    echo "No free Codex Desktop webview port found in CODEX_MULTI_LAUNCH_PORT_RANGE=$range" >&2
    return 1
}

parse_launcher_args() {
    local passthrough=0

    LAUNCHER_ARGS=()
    if early_truthy_env_value "${CODEX_MULTI_LAUNCH:-}"; then
        MULTI_LAUNCH_REQUESTED=1
    fi

    while [ "$#" -gt 0 ]; do
        if [ "$passthrough" -eq 1 ]; then
            LAUNCHER_ARGS+=("$1")
            shift
            continue
        fi

        case "$1" in
            --)
                passthrough=1
                LAUNCHER_ARGS+=("$1")
                ;;
            --new-instance|--multi-instance|--multi-launch)
                MULTI_LAUNCH_REQUESTED=1
                ;;
            *)
                LAUNCHER_ARGS+=("$1")
                ;;
        esac
        shift
    done
}

configure_multi_launch_instance() {
    parse_launcher_args "$@"
    [ "$MULTI_LAUNCH_REQUESTED" -eq 1 ] || return 0

    local base_state_dir="$APP_STATE_DIR"
    local selected_port

    selected_port="$(choose_multi_launch_port)" || exit 1
    CODEX_LINUX_WEBVIEW_PORT="$selected_port"
    CODEX_LINUX_INSTANCE_ID="port-$CODEX_LINUX_WEBVIEW_PORT"
    MULTI_LAUNCH_ACTIVE=1
    CODEX_LINUX_MULTI_LAUNCH=1

    APP_STATE_DIR="$base_state_dir/instances/$CODEX_LINUX_INSTANCE_ID"
    APP_PID_FILE="$APP_STATE_DIR/app.pid"
    WEBVIEW_PID_FILE="$APP_STATE_DIR/webview.pid"
    if [ -n "${XDG_RUNTIME_DIR:-}" ]; then
        LAUNCH_ACTION_RUNTIME_DIR="$XDG_RUNTIME_DIR/$CODEX_LINUX_APP_ID/instances/$CODEX_LINUX_INSTANCE_ID"
    else
        LAUNCH_ACTION_RUNTIME_DIR="$APP_STATE_DIR"
    fi
    LAUNCH_ACTION_SOCKET="$LAUNCH_ACTION_RUNTIME_DIR/launch-action.sock"
    LOG_FILE="$LOG_DIR/launcher-$CODEX_LINUX_INSTANCE_ID.log"

    if [ -n "${CODEX_ELECTRON_USER_DATA_DIR:-}" ]; then
        CODEX_ELECTRON_USER_DATA_DIR="$CODEX_ELECTRON_USER_DATA_DIR/instances/$CODEX_LINUX_INSTANCE_ID"
    else
        CODEX_ELECTRON_USER_DATA_DIR="$APP_STATE_DIR/electron-user-data"
    fi
    export CODEX_ELECTRON_USER_DATA_DIR CODEX_LINUX_INSTANCE_ID CODEX_LINUX_MULTI_LAUNCH CODEX_LINUX_WEBVIEW_PORT
}

configure_multi_launch_instance "$@"
WEBVIEW_ORIGIN="http://127.0.0.1:$CODEX_LINUX_WEBVIEW_PORT"

mkdir -p "$LOG_DIR" "$APP_CONFIG_DIR" "$APP_STATE_DIR" "$LAUNCH_ACTION_RUNTIME_DIR"
chmod 700 "$LAUNCH_ACTION_RUNTIME_DIR" 2>/dev/null || true
export CODEX_DESKTOP_LAUNCH_ACTION_SOCKET="$LAUNCH_ACTION_SOCKET"
STARTED_WEBVIEW_PID=""
ADOPTED_WEBVIEW_PID=""
ELECTRON_PID=""
RUNNING_APP_PID=""
WARM_START=0

if [[ "${LAUNCHER_ARGS[0]:-}" == "--help" || "${LAUNCHER_ARGS[0]:-}" == "-h" ]]; then
    cat <<HELP
Usage: ./start.sh [OPTIONS] [-- ELECTRON_FLAGS...]

Launches the $CODEX_LINUX_APP_DISPLAY_NAME app.

Options:
  -h, --help                  Show this help message and exit
  --new-instance              Start a separate app instance on the first free port in the multi-launch range
  --new-chat                  Open the main window on a new chat
  --quick-chat                Open a projectless quick chat
  --prompt-chat               Show the compact prompt for a new chat
  --hotkey-window             Alias for --prompt-chat
  --safe-mode                 X11 + software rendering fallback
  --disable-gpu               Disable Electron GPU acceleration
  --enable-gpu                Re-enable Electron GPU acceleration
  --x11                       Force X11/XWayland
  --wayland                   Force native Wayland

Default launch keeps Electron GPU enabled and lets Electron choose the platform.
Extra flags are passed directly to Electron.
Persistent launch flags: $USER_ELECTRON_FLAGS_FILE

Environment:
  CODEX_LINUX_RENDERING_MODE=auto|default|wslg|wayland-gpu
                              Auto-detect WSLg, keep generic Linux defaults, force the WSLg profile,
                              or force native Wayland with GPU compositing enabled
  CODEX_ELECTRON_DISABLE_GPU_COMPOSITING=0|1
                              Set to 1 to add --disable-gpu-compositing for flicker workarounds
  CODEX_FORCE_RENDERER_ACCESSIBILITY=auto|0|1
                              Override --force-renderer-accessibility; auto skips it under WSLg and wayland-gpu
  CODEX_MULTI_LAUNCH=1        Treat normal launches like --new-instance
  CODEX_MULTI_LAUNCH_PORT_RANGE=START-END
                              Port allocation range for --new-instance (default: CODEX_WEBVIEW_PORT through +4)

Logs: $LOG_FILE
HELP
    exit 0
fi

exec >>"$LOG_FILE" 2>&1

echo "[$(date -Is)] Starting $CODEX_LINUX_APP_DISPLAY_NAME launcher"
if [ "$MULTI_LAUNCH_ACTIVE" -eq 1 ]; then
    echo "Multi-launch instance: id=$CODEX_LINUX_INSTANCE_ID webview_port=$CODEX_LINUX_WEBVIEW_PORT state_dir=$APP_STATE_DIR user_data_dir=$CODEX_ELECTRON_USER_DATA_DIR"
fi

now_ms() {
    local value seconds nanos
    value="$(date +%s%N 2>/dev/null || true)"
    case "$value" in
        *N*|"") echo "$(($(date +%s) * 1000))" ;;
        *)
            seconds="${value:0:${#value}-9}"
            nanos="${value: -9}"
            echo "$((seconds * 1000 + 10#$nanos / 1000000))"
            ;;
    esac
}

LAUNCHER_START_MS="$(now_ms)"

log_phase() {
    local phase="$1"
    local elapsed_ms
    elapsed_ms="$(($(now_ms) - LAUNCHER_START_MS))"
    echo "[$(date -Is)] launcher_phase=$phase elapsedMs=$elapsed_ms"
}

make_tree_owner_writable() {
    local path="$1"

    [ -e "$path" ] || return 0
    chmod -R u+rwX "$path" 2>/dev/null || true
}

remove_tree_if_exists() {
    local path="$1"

    [ -e "$path" ] || return 0
    make_tree_owner_writable "$path"
    rm -rf "$path"
}

fix_bundled_marketplace_tmp_permissions() {
    local codex_home="${CODEX_HOME:-$HOME/.codex}"
    local marketplace_tmp_root="$codex_home/.tmp/bundled-marketplaces"

    [ -d "$marketplace_tmp_root" ] || return 0
    find "$marketplace_tmp_root" -type d -exec chmod u+rwx {} + 2>/dev/null || true
    find "$marketplace_tmp_root" -type f -exec chmod u+rw {} + 2>/dev/null || true
}

clear_bundled_marketplace_tmp_cache() {
    local codex_home="${CODEX_HOME:-$HOME/.codex}"
    local marketplace_tmp_root="$codex_home/.tmp/bundled-marketplaces"
    local path

    fix_bundled_marketplace_tmp_permissions
    for path in "$marketplace_tmp_root"/openai-bundled "$marketplace_tmp_root"/openai-bundled.staging-*; do
        [ -e "$path" ] || continue
        remove_tree_if_exists "$path"
    done
}

monitor_bundled_marketplace_tmp_permissions() {
    (
        local i=0
        while [ "$i" -lt 300 ]; do
            fix_bundled_marketplace_tmp_permissions
            sleep 0.1
            i=$((i + 1))
        done
    ) &
}

import_graphical_env_entry() {
    local entry="$1"
    local name="${entry%%=*}"

    [ "$entry" != "$name" ] || return 0

    # SOMMELIER_VERSION / SOMMELIER_VM_IDENTIFIER are imported here so the
    # ChromeOS / Crostini auto-X11 fallback in set_electron_defaults sees
    # them on app-launcher paths where they may only be present in the
    # systemd --user manager environment (and not in the spawned process's
    # initial env).
    case "$name" in
        DISPLAY|WAYLAND_DISPLAY|XDG_SESSION_TYPE|XDG_CURRENT_DESKTOP|XDG_SESSION_DESKTOP|DBUS_SESSION_BUS_ADDRESS|XDG_RUNTIME_DIR|DESKTOP_SESSION|XAUTHORITY|HYPRLAND_INSTANCE_SIGNATURE|YDOTOOL_SOCKET|SOMMELIER_VERSION|SOMMELIER_VM_IDENTIFIER)
            if [ -z "${!name:-}" ]; then
                export "$entry"
            fi
            ;;
    esac
}

import_graphical_env_from_proc() {
    local pid="$1"
    local entry
    local found_display=0

    [[ "$pid" =~ ^[0-9]+$ ]] || return 1
    [ -r "/proc/$pid/environ" ] || return 1

    while IFS= read -r -d '' entry; do
        case "$entry" in
            DISPLAY=*|WAYLAND_DISPLAY=*) found_display=1 ;;
        esac
        import_graphical_env_entry "$entry"
    done < "/proc/$pid/environ"

    [ "$found_display" -eq 1 ]
}

import_graphical_env_from_systemd_user() {
    local entry
    local found_display=0

    command -v systemctl >/dev/null 2>&1 || return 1

    while IFS= read -r entry; do
        case "$entry" in
            DISPLAY=*|WAYLAND_DISPLAY=*) found_display=1 ;;
        esac
        import_graphical_env_entry "$entry"
    done < <(systemctl --user show-environment 2>/dev/null || true)

    [ "$found_display" -eq 1 ]
}

discover_graphical_env_from_processes() {
    local status_file
    local pid
    local uid

    for status_file in /proc/[0-9]*/status; do
        [ -e "$status_file" ] || continue
        pid="${status_file#/proc/}"
        pid="${pid%/status}"
        uid="$(awk '/^Uid:/ {print $2}' "$status_file" 2>/dev/null || true)"
        [ "$uid" = "$(id -u)" ] || continue
        import_graphical_env_from_proc "$pid" && return 0
    done

    return 1
}

hydrate_graphical_session_env() {
    if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
        # Display vars are already present, but the ChromeOS / Crostini
        # auto-X11 fallback in set_electron_defaults also needs
        # SOMMELIER_VERSION. On some app-launcher paths the spawned
        # process inherits DISPLAY / WAYLAND_DISPLAY but not the Sommelier
        # markers (those live only in the systemd --user manager env). Try
        # one user-manager import in that case so the whitelisted
        # SOMMELIER_VERSION / SOMMELIER_VM_IDENTIFIER get hydrated.
        if [ -z "${SOMMELIER_VERSION:-}" ]; then
            import_graphical_env_from_systemd_user >/dev/null 2>&1 || true
        fi
        return 0
    fi

    import_graphical_env_from_systemd_user && return 0
    discover_graphical_env_from_processes && return 0
    return 0
}

desktop_entry_exists() {
    local desktop_name="$CODEX_LINUX_APP_ID.desktop"
    local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
    local data_dirs="${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
    local data_dir
    local -a data_dirs_array

    [ -f "$data_home/applications/$desktop_name" ] && return 0

    IFS=: read -r -a data_dirs_array <<< "$data_dirs"
    for data_dir in "${data_dirs_array[@]}"; do
        [ -f "$data_dir/applications/$desktop_name" ] && return 0
    done

    return 1
}

register_url_scheme_handlers() {
    command -v xdg-mime >/dev/null 2>&1 || return 0
    desktop_entry_exists || return 0

    local desktop_name="$CODEX_LINUX_APP_ID.desktop"
    local scheme
    local mime_type
    local current_handler

    for scheme in codex codex-browser-sidebar; do
        mime_type="x-scheme-handler/$scheme"
        current_handler="$(xdg-mime query default "$mime_type" 2>/dev/null || true)"
        [ "$current_handler" = "$desktop_name" ] && continue
        xdg-mime default "$desktop_name" "$mime_type" >/dev/null 2>&1 || true
    done
}

linux_setting_enabled() {
    local key="$1"
    local default_value="${2:-1}"

    python3 - "$APP_SETTINGS_FILE" "$key" "$default_value" <<'PY'
import json
import sys

settings_path, key, default_value = sys.argv[1:4]
enabled = default_value == "1"

try:
    with open(settings_path, "r", encoding="utf-8") as handle:
        data = json.load(handle)
    if isinstance(data, dict) and key in data:
        value = data[key]
        if isinstance(value, bool):
            enabled = value
        elif isinstance(value, (int, float)):
            enabled = value != 0
        elif isinstance(value, str):
            enabled = value.strip().lower() not in {"0", "false", "no", "off"}
except FileNotFoundError:
    pass
except (OSError, json.JSONDecodeError):
    pass

raise SystemExit(0 if enabled else 1)
PY
}

load_packaged_runtime_helper() {
    if [ -f "$PACKAGED_RUNTIME_HELPER" ]; then
        # shellcheck disable=SC1090
        . "$PACKAGED_RUNTIME_HELPER"
    fi
}

run_packaged_runtime_prelaunch() {
    if declare -F codex_packaged_runtime_prelaunch >/dev/null 2>&1; then
        codex_packaged_runtime_prelaunch
    fi
}

export_packaged_runtime_env() {
    if declare -F codex_packaged_runtime_export_env >/dev/null 2>&1; then
        codex_packaged_runtime_export_env
    fi
}

prepend_managed_node_runtime_to_path() {
    [ -x "$MANAGED_NODE_BIN_DIR/node" ] || return 0

    case ":$PATH:" in
        *":$MANAGED_NODE_BIN_DIR:"*) ;;
        *) export PATH="$MANAGED_NODE_BIN_DIR:$PATH" ;;
    esac
    export CODEX_MANAGED_NODE_RUNTIME_DIR="$SCRIPT_DIR/resources/node-runtime"
}

source_feature_env_files() {
    [ -n "${FEATURE_ENV_DIR:-}" ] || return 0
    [ -d "$FEATURE_ENV_DIR" ] || return 0

    local env_file
    local line
    local name
    local value
    for env_file in "$FEATURE_ENV_DIR"/*; do
        [ -f "$env_file" ] || continue
        echo "Loading Linux feature environment: $env_file"
        while IFS= read -r line || [ -n "$line" ]; do
            line="${line%$'\r'}"
            case "$line" in
                ""|\#*) continue ;;
                *=*) ;;
                *)
                    echo "Ignoring Linux feature env line without KEY=VALUE in $env_file"
                    continue
                    ;;
            esac
            name="${line%%=*}"
            value="${line#*=}"
            case "$name" in
                ""|[!A-Za-z_]*|*[!A-Za-z0-9_]*)
                    echo "Ignoring Linux feature env line with invalid key '$name' in $env_file"
                    continue
                    ;;
            esac
            export "$name=$value"
        done < "$env_file"
    done
}

run_feature_prelaunch_hooks() {
    [ -n "${FEATURE_PRELAUNCH_HOOK_DIR:-}" ] || return 0
    [ -d "$FEATURE_PRELAUNCH_HOOK_DIR" ] || return 0

    local hook
    for hook in "$FEATURE_PRELAUNCH_HOOK_DIR"/*; do
        [ -f "$hook" ] || continue
        [ -x "$hook" ] || continue
        echo "Running Linux feature prelaunch hook: $hook"
        CODEX_HOME="$CODEX_HOME" \
        CODEX_LINUX_APP_DIR="$SCRIPT_DIR" \
        CODEX_LINUX_APP_STATE_DIR="$APP_STATE_DIR" \
        CODEX_LINUX_FEATURES_DIR="$CODEX_LINUX_FEATURES_DIR" \
        CODEX_LINUX_LAUNCHER_LOG="$LOG_FILE" \
        CODEX_LINUX_LAUNCHER_CMD="${CODEX_LINUX_LAUNCHER_CMD:-$SCRIPT_DIR/start.sh}" \
        CODEX_LINUX_FEATURE_HOOK_PHASE="prelaunch" \
        CODEX_UPDATE_MANAGER_PATH="${CODEX_UPDATE_MANAGER_PATH:-}" \
            "$hook" "$SCRIPT_DIR" "$APP_STATE_DIR" "$LOG_DIR"
    done
}

bundled_plugin_version() {
    local plugin_json="$1"

    python3 - "$plugin_json" <<'PY'
import json
import sys

try:
    with open(sys.argv[1], "r", encoding="utf-8") as handle:
        version = json.load(handle).get("version")
except (OSError, json.JSONDecodeError):
    version = None

if not isinstance(version, str) or not version.strip():
    raise SystemExit(1)
print(version.strip())
PY
}

bundled_plugin_name() {
    local plugin_json="$1"

    python3 - "$plugin_json" <<'PY'
import json
import re
import sys

try:
    with open(sys.argv[1], "r", encoding="utf-8") as handle:
        name = json.load(handle).get("name")
except (OSError, json.JSONDecodeError):
    name = None

if not isinstance(name, str) or re.fullmatch(r"[A-Za-z0-9._-]+", name.strip()) is None:
    raise SystemExit(1)
print(name.strip())
PY
}

sync_browser_use_bundled_plugin_cache() {
    local source_plugin="$SCRIPT_DIR/resources/plugins/openai-bundled/plugins/browser"
    local source_marketplace="$SCRIPT_DIR/resources/plugins/openai-bundled/.agents/plugins/marketplace.json"
    local plugin_json=""
    local source_client=""
    local codex_home="${CODEX_HOME:-$HOME/.codex}"
    local plugin_name
    local plugin_dir_name
    local version
    local cache_root
    local cache_plugin
    local cache_client
    local cache_parent
    local tmp_plugin
    local marketplace_root
    local marketplace_plugins_dir
    local marketplace_plugin_link
    local needs_copy=1

    plugin_json="$source_plugin/.codex-plugin/plugin.json"
    source_client="$source_plugin/scripts/browser-client.mjs"

    [ -f "$plugin_json" ] || return 0
    [ -f "$source_client" ] || return 0

    plugin_name="$(bundled_plugin_name "$plugin_json" 2>/dev/null || basename "$source_plugin")"
    plugin_dir_name="$(basename "$source_plugin")"
    [ -n "$plugin_name" ] || return 0
    [ -n "$plugin_dir_name" ] || return 0

    marketplace_root="$codex_home/.tmp/bundled-marketplaces/openai-bundled"
    marketplace_plugins_dir="$marketplace_root/.agents/plugins"
    marketplace_plugin_link="$marketplace_root/plugins/$plugin_dir_name"
    if [ -f "$source_marketplace" ]; then
        mkdir -p "$marketplace_plugins_dir" "$marketplace_root/plugins"
        rm -f "$marketplace_plugins_dir/marketplace.json"
        cp "$source_marketplace" "$marketplace_plugins_dir/marketplace.json" && \
            chmod u+w "$marketplace_plugins_dir/marketplace.json" || \
            echo "Browser Use bundled marketplace sync failed; continuing with existing marketplace cache."
    fi

    version="$(bundled_plugin_version "$plugin_json" 2>/dev/null || true)"
    [ -n "$version" ] || return 0

    cache_root="$codex_home/plugins/cache/openai-bundled/$plugin_name"
    cache_plugin="$cache_root/$version"
    cache_client="$cache_plugin/scripts/browser-client.mjs"

    if [ -f "$cache_client" ] && cmp -s "$source_client" "$cache_client"; then
        needs_copy=0
    fi

    if [ "$needs_copy" -eq 1 ]; then
        cache_parent="$(dirname "$cache_plugin")"
        tmp_plugin="$cache_parent/.browser-$version.tmp.$$"
        remove_tree_if_exists "$tmp_plugin"
        mkdir -p "$cache_parent"

        if cp -R "$source_plugin" "$tmp_plugin"; then
            make_tree_owner_writable "$tmp_plugin"
            find "$tmp_plugin" -type f -name '*:com.apple.*' -delete
            remove_tree_if_exists "$cache_plugin"
            mv "$tmp_plugin" "$cache_plugin"
            echo "Browser Use plugin cache synced from bundled resources: $cache_plugin"
        else
            remove_tree_if_exists "$tmp_plugin"
            echo "Browser Use plugin cache sync failed; continuing with existing cache."
            return 0
        fi
    fi

    if [ -e "$cache_root/latest" ] && [ ! -L "$cache_root/latest" ]; then
        remove_tree_if_exists "$cache_root/latest"
    fi
    ln -sfn "$version" "$cache_root/latest"

    mkdir -p "$marketplace_root/plugins"
    if [ -e "$marketplace_plugin_link" ] && [ ! -L "$marketplace_plugin_link" ]; then
        remove_tree_if_exists "$marketplace_plugin_link"
    fi
    ln -sfn "$cache_root/latest" "$marketplace_plugin_link"
}

chrome_extension_host_arch() {
    case "$(uname -m)" in
        x86_64) echo "x64" ;;
        aarch64|arm64) echo "arm64" ;;
        *) return 1 ;;
    esac
}

write_chrome_native_host_manifests() {
    local host_path="$1"
    local plugin_dir="$2"

    python3 - "$host_path" "$HOME" "$plugin_dir" "$SCRIPT_DIR" <<'PY'
import json
import pathlib
import re
import sys

host_path = sys.argv[1]
home = pathlib.Path(sys.argv[2])
plugin_dir = pathlib.Path(sys.argv[3])
app_dir = pathlib.Path(sys.argv[4])
scripts_dir = plugin_dir / "scripts"

extension_id = None
host_name = None

extension_id_json = scripts_dir / "extension-id.json"
try:
    data = json.loads(extension_id_json.read_text(encoding="utf-8"))
    extension_id = data.get("extensionId")
    host_name = data.get("extensionHostName")
except OSError:
    pass

if extension_id is None or host_name is None:
    install_manifest = (scripts_dir / "installManifest.mjs").read_text(encoding="utf-8")
    extension_id_match = re.search(r'extensionId\s*:\s*"([a-p]{32})"', install_manifest)
    host_name_match = re.search(r'extensionHostName\s*:\s*"([A-Za-z0-9_.]+)"', install_manifest)
    if extension_id is None and extension_id_match is not None:
        extension_id = extension_id_match.group(1)
    if host_name is None and host_name_match is not None:
        host_name = host_name_match.group(1)

if not isinstance(extension_id, str) or re.fullmatch(r"[a-p]{32}", extension_id) is None:
    raise SystemExit("Invalid Chrome extension id in bundled plugin metadata")
if not isinstance(host_name, str) or re.fullmatch(r"[A-Za-z0-9_.]+", host_name) is None:
    raise SystemExit("Invalid Chrome native host name in bundled plugin metadata")

manifest_name = f"{host_name}.json"
manifest = {
    "name": host_name,
    "description": "Codex chrome native messaging host",
    "type": "stdio",
    "path": host_path,
    "allowed_origins": [f"chrome-extension://{extension_id}/"],
}
text = json.dumps(manifest, separators=(",", ":"))

manifest_locations = [
    ".config/google-chrome/NativeMessagingHosts",
    ".config/BraveSoftware/Brave-Browser/NativeMessagingHosts",
    ".config/chromium/NativeMessagingHosts",
]

extra_locations = app_dir / ".codex-linux" / "chrome-native-host-manifest-paths"
try:
    for line in extra_locations.read_text(encoding="utf-8").splitlines():
        relative = line.strip()
        if (
            not relative
            or relative.startswith("#")
            or pathlib.PurePosixPath(relative).is_absolute()
            or ".." in pathlib.PurePosixPath(relative).parts
        ):
            continue
        if relative not in manifest_locations:
            manifest_locations.append(relative)
except OSError:
    pass

for relative in manifest_locations:
    directory = home / relative
    directory.mkdir(parents=True, exist_ok=True)
    path = directory / manifest_name
    try:
        if path.read_text(encoding="utf-8") == text:
            continue
    except OSError:
        pass
    path.write_text(text, encoding="utf-8")
PY
}

sync_chrome_bundled_plugin_cache() {
    local source_plugin="$SCRIPT_DIR/resources/plugins/openai-bundled/plugins/chrome"
    local source_marketplace="$SCRIPT_DIR/resources/plugins/openai-bundled/.agents/plugins/marketplace.json"
    local plugin_json="$source_plugin/.codex-plugin/plugin.json"
    local codex_home="${CODEX_HOME:-$HOME/.codex}"
    local extension_arch
    local source_host
    local source_client
    local source_install_manifest
    local version
    local cache_root
    local cache_plugin
    local cache_host
    local cache_client
    local cache_install_manifest
    local cache_parent
    local tmp_plugin
    local marketplace_root
    local marketplace_plugins_dir
    local marketplace_plugin_link
    local host_path
    local needs_copy=1

    [ -f "$plugin_json" ] || return 0
    [ -d "$source_plugin" ] || return 0

    if ! extension_arch="$(chrome_extension_host_arch)"; then
        return 0
    fi

    source_host="$source_plugin/extension-host/linux/$extension_arch/extension-host"
    source_client="$source_plugin/scripts/browser-client.mjs"
    source_install_manifest="$source_plugin/scripts/installManifest.mjs"
    [ -x "$source_host" ] || return 0
    [ -f "$source_client" ] || return 0
    [ -f "$source_install_manifest" ] || return 0

    version="$(bundled_plugin_version "$plugin_json" 2>/dev/null || true)"
    [ -n "$version" ] || return 0

    cache_root="$codex_home/plugins/cache/openai-bundled/chrome"
    cache_plugin="$cache_root/$version"
    cache_host="$cache_plugin/extension-host/linux/$extension_arch/extension-host"
    cache_client="$cache_plugin/scripts/browser-client.mjs"
    cache_install_manifest="$cache_plugin/scripts/installManifest.mjs"

    if [ -f "$cache_plugin/.codex-plugin/plugin.json" ] && \
        [ -f "$cache_host" ] && \
        [ -f "$cache_client" ] && \
        [ -f "$cache_install_manifest" ] && \
        cmp -s "$plugin_json" "$cache_plugin/.codex-plugin/plugin.json" && \
        cmp -s "$source_host" "$cache_host" && \
        cmp -s "$source_client" "$cache_client" && \
        cmp -s "$source_install_manifest" "$cache_install_manifest"; then
        needs_copy=0
    fi

    if [ "$needs_copy" -eq 0 ]; then
        local relative
        for relative in \
            scripts/check-extension-installed.js \
            scripts/check-native-host-manifest.js \
            scripts/chrome-is-running.js \
            scripts/extension-id.json \
            scripts/installed-browsers.js \
            scripts/open-chrome-window.js; do
            [ -f "$source_plugin/$relative" ] || continue
            if [ ! -f "$cache_plugin/$relative" ] || ! cmp -s "$source_plugin/$relative" "$cache_plugin/$relative"; then
                needs_copy=1
                break
            fi
        done
    fi

    if [ "$needs_copy" -eq 1 ]; then
        cache_parent="$(dirname "$cache_plugin")"
        tmp_plugin="$cache_parent/.chrome-$version.tmp.$$"
        remove_tree_if_exists "$tmp_plugin"
        mkdir -p "$cache_parent"

        if cp -R "$source_plugin" "$tmp_plugin"; then
            make_tree_owner_writable "$tmp_plugin"
            find "$tmp_plugin" -type f -name '*:com.apple.*' -delete
            remove_tree_if_exists "$cache_plugin"
            mv "$tmp_plugin" "$cache_plugin"
            echo "Chrome plugin cache synced from bundled resources: $cache_plugin"
        else
            remove_tree_if_exists "$tmp_plugin"
            echo "Chrome plugin cache sync failed; continuing with existing cache."
            return 0
        fi
    fi

    if [ -e "$cache_root/latest" ] && [ ! -L "$cache_root/latest" ]; then
        remove_tree_if_exists "$cache_root/latest"
    fi
    ln -sfn "$version" "$cache_root/latest"

    marketplace_root="$codex_home/.tmp/bundled-marketplaces/openai-bundled"
    marketplace_plugins_dir="$marketplace_root/.agents/plugins"
    marketplace_plugin_link="$marketplace_root/plugins/chrome"
    mkdir -p "$marketplace_plugins_dir" "$marketplace_root/plugins"
    if [ -f "$source_marketplace" ]; then
        rm -f "$marketplace_plugins_dir/marketplace.json"
        cp "$source_marketplace" "$marketplace_plugins_dir/marketplace.json" && \
            chmod u+w "$marketplace_plugins_dir/marketplace.json" || \
            echo "Chrome bundled marketplace sync failed; continuing with existing marketplace cache."
    fi
    if [ -e "$marketplace_plugin_link" ] && [ ! -L "$marketplace_plugin_link" ]; then
        remove_tree_if_exists "$marketplace_plugin_link"
    fi
    ln -sfn "$cache_root/latest" "$marketplace_plugin_link"

    host_path="$cache_root/latest/extension-host/linux/$extension_arch/extension-host"
    [ -x "$host_path" ] || return 0
    write_chrome_native_host_manifests "$host_path" "$cache_root/latest" || \
        echo "Chrome native host manifest sync failed; continuing with existing browser manifests."
}

sync_computer_use_bundled_plugin_cache() {
    local source_plugin="$SCRIPT_DIR/resources/plugins/openai-bundled/plugins/computer-use"
    local source_marketplace="$SCRIPT_DIR/resources/plugins/openai-bundled/.agents/plugins/marketplace.json"
    local plugin_json="$source_plugin/.codex-plugin/plugin.json"
    local source_backend="$source_plugin/bin/codex-computer-use-linux"
    local source_cosmic_helper="$source_plugin/bin/codex-computer-use-cosmic"
    local codex_home="${CODEX_HOME:-$HOME/.codex}"
    local version
    local cache_root
    local cache_plugin
    local cache_backend
    local cache_cosmic_helper
    local cache_parent
    local tmp_plugin
    local marketplace_root
    local marketplace_plugins_dir
    local marketplace_plugin_link
    local needs_copy=1

    [ -f "$plugin_json" ] || return 0
    [ -f "$source_backend" ] || return 0
    [ -f "$source_cosmic_helper" ] || return 0

    marketplace_root="$codex_home/.tmp/bundled-marketplaces/openai-bundled"
    marketplace_plugins_dir="$marketplace_root/.agents/plugins"
    marketplace_plugin_link="$marketplace_root/plugins/computer-use"
    if [ -f "$source_marketplace" ]; then
        mkdir -p "$marketplace_plugins_dir" "$marketplace_root/plugins"
        rm -f "$marketplace_plugins_dir/marketplace.json"
        cp "$source_marketplace" "$marketplace_plugins_dir/marketplace.json" && \
            chmod u+w "$marketplace_plugins_dir/marketplace.json" || \
            echo "Computer Use bundled marketplace sync failed; continuing with existing marketplace cache."
    fi

    version="$(bundled_plugin_version "$plugin_json" 2>/dev/null || true)"
    [ -n "$version" ] || return 0

    cache_root="$codex_home/plugins/cache/openai-bundled/computer-use"
    cache_plugin="$cache_root/$version"
    cache_backend="$cache_plugin/bin/codex-computer-use-linux"
    cache_cosmic_helper="$cache_plugin/bin/codex-computer-use-cosmic"

    if [ -f "$cache_backend" ] && [ -f "$cache_cosmic_helper" ] && \
        cmp -s "$source_backend" "$cache_backend" && \
        cmp -s "$source_cosmic_helper" "$cache_cosmic_helper"; then
        needs_copy=0
    fi

    if [ "$needs_copy" -eq 1 ]; then
        cache_parent="$(dirname "$cache_plugin")"
        tmp_plugin="$cache_parent/.computer-use-$version.tmp.$$"
        remove_tree_if_exists "$tmp_plugin"
        mkdir -p "$cache_parent"

        if cp -R "$source_plugin" "$tmp_plugin"; then
            make_tree_owner_writable "$tmp_plugin"
            find "$tmp_plugin" -type f -name '*:com.apple.*' -delete
            remove_tree_if_exists "$cache_plugin"
            mv "$tmp_plugin" "$cache_plugin"
            echo "Computer Use plugin cache synced from bundled resources: $cache_plugin"
        else
            remove_tree_if_exists "$tmp_plugin"
            echo "Computer Use plugin cache sync failed; continuing with existing cache."
            return 0
        fi
    fi

    if [ -e "$cache_root/latest" ] && [ ! -L "$cache_root/latest" ]; then
        remove_tree_if_exists "$cache_root/latest"
    fi
    ln -sfn "$version" "$cache_root/latest"

    mkdir -p "$marketplace_root/plugins"
    if [ -e "$marketplace_plugin_link" ] && [ ! -L "$marketplace_plugin_link" ]; then
        remove_tree_if_exists "$marketplace_plugin_link"
    fi
    ln -sfn "$cache_root/latest" "$marketplace_plugin_link"
}

sync_read_aloud_bundled_plugin_cache() {
    local source_plugin="$SCRIPT_DIR/resources/plugins/openai-bundled/plugins/read-aloud"
    local source_marketplace="$SCRIPT_DIR/resources/plugins/openai-bundled/.agents/plugins/marketplace.json"
    local plugin_json="$source_plugin/.codex-plugin/plugin.json"
    local source_backend="$source_plugin/bin/codex-read-aloud-linux"
    local source_runner="$source_plugin/bin/kokoro-stdin"
    local source_python_runner="$source_plugin/bin/kokoro_stdin.py"
    local codex_home="${CODEX_HOME:-$HOME/.codex}"
    local version
    local cache_root
    local cache_plugin
    local cache_backend
    local cache_runner
    local cache_python_runner
    local cache_parent
    local tmp_plugin
    local marketplace_root
    local marketplace_plugins_dir
    local marketplace_plugin_link
    local needs_copy=1

    [ -f "$plugin_json" ] || return 0
    [ -x "$source_backend" ] || return 0
    [ -x "$source_runner" ] || return 0
    [ -f "$source_python_runner" ] || return 0

    marketplace_root="$codex_home/.tmp/bundled-marketplaces/openai-bundled"
    marketplace_plugins_dir="$marketplace_root/.agents/plugins"
    marketplace_plugin_link="$marketplace_root/plugins/read-aloud"
    if [ -f "$source_marketplace" ]; then
        mkdir -p "$marketplace_plugins_dir" "$marketplace_root/plugins"
        rm -f "$marketplace_plugins_dir/marketplace.json"
        cp "$source_marketplace" "$marketplace_plugins_dir/marketplace.json" && \
            chmod u+w "$marketplace_plugins_dir/marketplace.json" || \
            echo "Read Aloud bundled marketplace sync failed; continuing with existing marketplace cache."
    fi

    version="$(bundled_plugin_version "$plugin_json" 2>/dev/null || true)"
    [ -n "$version" ] || return 0

    cache_root="$codex_home/plugins/cache/openai-bundled/read-aloud"
    cache_plugin="$cache_root/$version"
    cache_backend="$cache_plugin/bin/codex-read-aloud-linux"
    cache_runner="$cache_plugin/bin/kokoro-stdin"
    cache_python_runner="$cache_plugin/bin/kokoro_stdin.py"

    if [ -f "$cache_backend" ] && [ -f "$cache_runner" ] && [ -f "$cache_python_runner" ] && \
        cmp -s "$source_backend" "$cache_backend" && \
        cmp -s "$source_runner" "$cache_runner" && \
        cmp -s "$source_python_runner" "$cache_python_runner"; then
        needs_copy=0
    fi

    if [ "$needs_copy" -eq 1 ]; then
        cache_parent="$(dirname "$cache_plugin")"
        tmp_plugin="$cache_parent/.read-aloud-$version.tmp.$$"
        remove_tree_if_exists "$tmp_plugin"
        mkdir -p "$cache_parent"

        if cp -R "$source_plugin" "$tmp_plugin"; then
            make_tree_owner_writable "$tmp_plugin"
            find "$tmp_plugin" -type f -name '*:com.apple.*' -delete
            remove_tree_if_exists "$cache_plugin"
            mv "$tmp_plugin" "$cache_plugin"
            echo "Read Aloud plugin cache synced from bundled resources: $cache_plugin"
        else
            remove_tree_if_exists "$tmp_plugin"
            echo "Read Aloud plugin cache sync failed; continuing with existing cache."
            return 0
        fi
    fi

    if [ -e "$cache_root/latest" ] && [ ! -L "$cache_root/latest" ]; then
        remove_tree_if_exists "$cache_root/latest"
    fi
    ln -sfn "$version" "$cache_root/latest"

    mkdir -p "$marketplace_root/plugins"
    if [ -e "$marketplace_plugin_link" ] && [ ! -L "$marketplace_plugin_link" ]; then
        remove_tree_if_exists "$marketplace_plugin_link"
    fi
    ln -sfn "$cache_root/latest" "$marketplace_plugin_link"
}

resolve_browser_use_runtime_env() {
    if [ -z "${CODEX_ELECTRON_RESOURCES_PATH:-}" ]; then
        export CODEX_ELECTRON_RESOURCES_PATH="$SCRIPT_DIR/resources"
    fi

    if [ -z "${CODEX_BROWSER_USE_NODE_PATH:-}" ]; then
        if [ -x "$MANAGED_NODE_BIN_DIR/node" ]; then
            export CODEX_BROWSER_USE_NODE_PATH="$MANAGED_NODE_BIN_DIR/node"
        elif [ -x "$SCRIPT_DIR/resources/node" ]; then
            export CODEX_BROWSER_USE_NODE_PATH="$SCRIPT_DIR/resources/node"
        elif command -v node >/dev/null 2>&1; then
            CODEX_BROWSER_USE_NODE_PATH="$(command -v node)"
            export CODEX_BROWSER_USE_NODE_PATH
        fi
    fi

    if [ -z "${NODE_REPL_NODE_PATH:-}" ] && [ -n "${CODEX_BROWSER_USE_NODE_PATH:-}" ]; then
        export NODE_REPL_NODE_PATH="$CODEX_BROWSER_USE_NODE_PATH"
    fi

    if [ -z "${CODEX_NODE_REPL_PATH:-}" ]; then
        codex_runtime_node_repl="${XDG_CACHE_HOME:-$HOME/.cache}/codex-runtimes/codex-primary-runtime/dependencies/bin/node_repl"
        if [ -x "$SCRIPT_DIR/resources/node_repl" ]; then
            export CODEX_NODE_REPL_PATH="$SCRIPT_DIR/resources/node_repl"
        elif command -v node_repl >/dev/null 2>&1; then
            CODEX_NODE_REPL_PATH="$(command -v node_repl)"
            export CODEX_NODE_REPL_PATH
        elif [ -x "$codex_runtime_node_repl" ]; then
            export CODEX_NODE_REPL_PATH="$codex_runtime_node_repl"
        fi
    fi

    if [ -z "${CODEX_NODE_REPL_PATH:-}" ]; then
        echo "Browser Use node_repl runtime not found; in-app browser automation may be unavailable."
    fi
}

run_cold_start_hooks() {
    [ -d "$COLD_START_HOOK_DIR" ] || return 0
    local hook
    for hook in "$COLD_START_HOOK_DIR"/*; do
        [ -f "$hook" ] || continue
        [ -x "$hook" ] || continue
        echo "Starting cold-start hook: $hook"
        (
            CODEX_HOME="$CODEX_HOME" \
            CODEX_LINUX_APP_DIR="$SCRIPT_DIR" \
            CODEX_LINUX_APP_STATE_DIR="$APP_STATE_DIR" \
            CODEX_LINUX_FEATURES_DIR="$CODEX_LINUX_FEATURES_DIR" \
            CODEX_LINUX_LAUNCHER_LOG="$LOG_FILE" \
            CODEX_LINUX_LAUNCHER_CMD="${CODEX_LINUX_LAUNCHER_CMD:-$SCRIPT_DIR/start.sh}" \
            CODEX_LINUX_FEATURE_HOOK_PHASE="cold-start" \
            CODEX_UPDATE_MANAGER_PATH="${CODEX_UPDATE_MANAGER_PATH:-}" \
            "$hook" "$SCRIPT_DIR" "$APP_STATE_DIR" "$LOG_DIR"
        ) >>"$LOG_FILE" 2>&1 &
    done
}

run_feature_after_exit_hooks() {
    [ -n "${FEATURE_AFTER_EXIT_HOOK_DIR:-}" ] || return 0
    [ -d "$FEATURE_AFTER_EXIT_HOOK_DIR" ] || return 0

    local electron_status="${1:-0}"
    local hook hook_status
    for hook in "$FEATURE_AFTER_EXIT_HOOK_DIR"/*; do
        [ -f "$hook" ] || continue
        [ -x "$hook" ] || continue
        echo "Running Linux feature after-exit hook: $hook"
        set +e
        CODEX_HOME="$CODEX_HOME" \
        CODEX_LINUX_APP_DIR="$SCRIPT_DIR" \
        CODEX_LINUX_APP_STATE_DIR="$APP_STATE_DIR" \
        CODEX_LINUX_FEATURES_DIR="$CODEX_LINUX_FEATURES_DIR" \
        CODEX_LINUX_LAUNCHER_LOG="$LOG_FILE" \
        CODEX_LINUX_LAUNCHER_CMD="${CODEX_LINUX_LAUNCHER_CMD:-$SCRIPT_DIR/start.sh}" \
        CODEX_LINUX_FEATURE_HOOK_PHASE="after-exit" \
        CODEX_LINUX_ELECTRON_EXIT_STATUS="$electron_status" \
        CODEX_UPDATE_MANAGER_PATH="${CODEX_UPDATE_MANAGER_PATH:-}" \
            "$hook" "$SCRIPT_DIR" "$APP_STATE_DIR" "$LOG_DIR" "$electron_status"
        hook_status=$?
        set -e
        if [ "$hook_status" -ne 0 ]; then
            echo "Linux feature after-exit hook failed with status $hook_status: $hook"
        fi
    done
}

run_cli_preflight() {
    local allow_install_missing="${1:-0}"
    if ! has_update_manager; then
        if [ "$allow_install_missing" = "1" ]; then
            return 1
        fi
        return 0
    fi

    local -a preflight_args=(
        cli-preflight
        --print-path
    )
    if [ -n "${CODEX_CLI_PATH:-}" ]; then
        preflight_args+=(--cli-path "$CODEX_CLI_PATH")
    fi
    if [ "$allow_install_missing" = "1" ]; then
        preflight_args+=(--allow-install-missing)
    fi

    local refreshed_path=""
    if ! refreshed_path="$(run_update_manager "${preflight_args[@]}")"; then
        if [ "$allow_install_missing" = "1" ]; then
            return 1
        fi
        notify_error "Codex CLI prelaunch check failed. Continuing with the current CLI state. Check the launcher and updater logs if Codex Desktop misbehaves."
        return 0
    fi

    if [ -n "$refreshed_path" ]; then
        CODEX_CLI_PATH="$refreshed_path"
        export CODEX_CLI_PATH
    fi
}

run_cli_preflight_background() {
    if ! has_update_manager; then
        return 0
    fi

    (
        local -a args=(cli-preflight --print-path)
        if [ -n "${CODEX_CLI_PATH:-}" ]; then
            args+=(--cli-path "$CODEX_CLI_PATH")
        fi
        if ! run_update_manager "${args[@]}" >/dev/null 2>&1; then
            echo "Codex CLI background preflight failed. Continuing with the current CLI."
        fi
    ) &
}

is_interactive_terminal() {
    [ -t 0 ] && [ -t 1 ]
}

run_gui_cli_prompt() {
    if ! has_update_manager; then
        return 1
    fi

    local refreshed_path=""
    local -a args=(prompt-install-cli --print-path)
    if [ -n "${CODEX_CLI_PATH:-}" ]; then
        args+=(--cli-path "$CODEX_CLI_PATH")
    fi
    if ! refreshed_path="$(run_update_manager "${args[@]}")"; then
        return 1
    fi

    if [ -n "$refreshed_path" ]; then
        CODEX_CLI_PATH="$refreshed_path"
        export CODEX_CLI_PATH
    fi

    return 0
}

prompt_install_missing_cli() {
    if ! is_interactive_terminal; then
        return 1
    fi

    if ! has_update_manager; then
        return 1
    fi

    local reply=""
    printf 'Codex CLI is not installed. Install it now? [Y/n] '
    if ! read -r reply; then
        return 1
    fi

    case "$reply" in
        ""|y|Y|yes|YES|Yes)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

resolve_notification_icon() {
    local candidate
    for candidate in \
        "$APP_NOTIFICATION_ICON_BUNDLE" \
        "$APP_NOTIFICATION_ICON_SYSTEM" \
        "$APP_NOTIFICATION_ICON_REPO"
    do
        if [ -f "$candidate" ]; then
            echo "$candidate"
            return 0
        fi
    done

    echo "$APP_NOTIFICATION_ICON_NAME"
}

find_codex_cli() {
    if command -v codex >/dev/null 2>&1; then
        command -v codex
        return 0
    fi

    local nvm_dir="${NVM_DIR:-}"
    if [ -z "$nvm_dir" ]; then
        if [ -s "${XDG_CONFIG_HOME:-$HOME/.config}/nvm/nvm.sh" ]; then
            nvm_dir="${XDG_CONFIG_HOME:-$HOME/.config}/nvm"
        else
            nvm_dir="$HOME/.nvm"
        fi
    fi
    if [ -s "$nvm_dir/nvm.sh" ]; then
        export NVM_DIR="$nvm_dir"
        # shellcheck disable=SC1090
        . "$NVM_DIR/nvm.sh" >/dev/null 2>&1 || true
        if command -v codex >/dev/null 2>&1; then
            command -v codex
            return 0
        fi
    fi

    local candidate
    for candidate in \
        "$HOME/.bun/bin/codex" \
        "$HOME/.npm-global/bin/codex" \
        "${XDG_CONFIG_HOME:-$HOME/.config}/nvm/versions/node/current/bin/codex" \
        "${XDG_CONFIG_HOME:-$HOME/.config}/nvm/versions/node"/*/bin/codex \
        "$HOME/.nvm/versions/node/current/bin/codex" \
        "$HOME/.nvm/versions/node"/*/bin/codex \
        "$HOME/.local/share/pnpm/codex" \
        "$HOME/.local/bin/codex" \
        "/usr/local/bin/codex" \
        "/usr/bin/codex"
    do
        if [ -x "$candidate" ]; then
            echo "$candidate"
            return 0
        fi
    done

    return 1
}

notify_error() {
    local message="$1"
    local icon
    icon="$(resolve_notification_icon)"
    echo "$message"
    if command -v notify-send >/dev/null 2>&1; then
        notify-send \
            -a "$CODEX_LINUX_APP_DISPLAY_NAME" \
            -i "$icon" \
            -h "string:desktop-entry:$CODEX_LINUX_APP_ID" \
            "$CODEX_LINUX_APP_DISPLAY_NAME" \
            "$message"
    fi
}

canonical_path() {
    readlink -f "$1" 2>/dev/null || echo "$1"
}

resolve_update_manager_path() {
    if [ -n "${CODEX_UPDATE_MANAGER_PATH:-}" ] && [ -x "$CODEX_UPDATE_MANAGER_PATH" ]; then
        return 0
    fi

    local candidate
    for candidate in \
        "$SCRIPT_DIR/../target/release/codex-update-manager" \
        "$SCRIPT_DIR/codex-update-manager" \
        "/usr/bin/codex-update-manager" \
        "/usr/local/bin/codex-update-manager"
    do
        if [ -x "$candidate" ]; then
            CODEX_UPDATE_MANAGER_PATH="$(canonical_path "$candidate")"
            export CODEX_UPDATE_MANAGER_PATH
            return 0
        fi
    done

    if command -v codex-update-manager >/dev/null 2>&1; then
        CODEX_UPDATE_MANAGER_PATH="$(command -v codex-update-manager)"
        export CODEX_UPDATE_MANAGER_PATH
        return 0
    fi

    return 1
}

has_update_manager() {
    resolve_update_manager_path >/dev/null 2>&1
}

export_feature_runtime_env() {
    CODEX_LINUX_APP_DIR="$SCRIPT_DIR"
    CODEX_LINUX_APP_STATE_DIR="$APP_STATE_DIR"
    CODEX_LINUX_LAUNCHER_CMD="$SCRIPT_DIR/start.sh"
    export CODEX_LINUX_APP_DIR CODEX_LINUX_APP_STATE_DIR CODEX_LINUX_LAUNCHER_CMD
    resolve_update_manager_path >/dev/null 2>&1 || true
}

run_update_manager() {
    resolve_update_manager_path || return 127
    "$CODEX_UPDATE_MANAGER_PATH" "$@"
}

pid_is_current_user() {
    local pid="$1"
    local uid

    uid="$(awk '/^Uid:/ {print $2}' "/proc/$pid/status" 2>/dev/null || true)"
    [ "$uid" = "$(id -u)" ]
}

pid_is_electron_helper() {
    local pid="$1"
    [ -r "/proc/$pid/cmdline" ] || return 1
    tr '\0' '\n' < "/proc/$pid/cmdline" 2>/dev/null | grep -q '^--type='
}

pid_matches_executable() {
    local pid="$1"
    local expected="$2"
    local actual

    [[ "$pid" =~ ^[0-9]+$ ]] || return 1
    [ -d "/proc/$pid" ] || return 1
    pid_is_current_user "$pid" || return 1
    actual="$(readlink -f "/proc/$pid/exe" 2>/dev/null || true)"
    [ "$actual" = "$(canonical_path "$expected")" ] || return 1
    ! pid_is_electron_helper "$pid"
}

find_running_app_pid() {
    local pid

    if [ -f "$APP_PID_FILE" ]; then
        pid="$(cat "$APP_PID_FILE" 2>/dev/null || true)"
        if pid_matches_executable "$pid" "$SCRIPT_DIR/electron"; then
            echo "$pid"
            return 0
        fi
    fi

    return 1
}

discover_running_app_pid() {
    local pid
    local proc_exe

    for proc_exe in /proc/[0-9]*/exe; do
        [ -e "$proc_exe" ] || continue
        pid="${proc_exe#/proc/}"
        pid="${pid%/exe}"
        if pid_matches_executable "$pid" "$SCRIPT_DIR/electron"; then
            echo "$pid"
            return 0
        fi
    done

    return 1
}

running_app_is_active() {
    [ -n "${RUNNING_APP_PID:-}" ] && pid_matches_executable "$RUNNING_APP_PID" "$SCRIPT_DIR/electron"
}

using_second_instance_handoff() {
    [ "$WARM_START" -eq 0 ] && running_app_is_active
}

needs_cold_start() {
    [ "$WARM_START" -eq 0 ] && ! using_second_instance_handoff
}

detect_warm_start() {
    if RUNNING_APP_PID="$(find_running_app_pid)" || { [ -S "$LAUNCH_ACTION_SOCKET" ] && RUNNING_APP_PID="$(discover_running_app_pid)"; }; then
        echo "$RUNNING_APP_PID" > "$APP_PID_FILE"
        if ! linux_setting_enabled "codex-linux-warm-start-enabled" 1; then
            WARM_START=0
            echo "Warm-start handoff disabled by $APP_SETTINGS_FILE"
            echo "Detected running Codex Desktop pid=$RUNNING_APP_PID; preserving liveness marker for second-instance handoff"
            return 0
        fi

        WARM_START=1
        echo "Detected running Codex Desktop pid=$RUNNING_APP_PID; using warm-start handoff"
        return 0
    fi

    if ! linux_setting_enabled "codex-linux-warm-start-enabled" 1; then
        WARM_START=0
        echo "Warm-start handoff disabled by $APP_SETTINGS_FILE"
    fi
}

send_warm_start_launch_action() {
    [ "$WARM_START" -eq 1 ] || return 1
    [ -S "$LAUNCH_ACTION_SOCKET" ] || return 1

    python3 - "$LAUNCH_ACTION_SOCKET" "$@" <<'PY'
import json
import socket
import sys

socket_path = sys.argv[1]
argv = sys.argv[2:]
payload = json.dumps({"argv": argv}, separators=(",", ":")).encode("utf-8") + b"\n"

client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.settimeout(1.0)
try:
    client.connect(socket_path)
    client.sendall(payload)
finally:
    client.close()
PY
}

webview_origin_is_reachable() {
    verify_webview_origin "$WEBVIEW_ORIGIN/index.html" >/dev/null 2>&1
}

webview_origin_is_reachable_fast() {
    local url="$WEBVIEW_ORIGIN/index.html"
    local body err

    err=$(mktemp 2>/dev/null) || err="${TMPDIR:-/tmp}/codex-curl-err.$$"
    if ! body="$(curl --disable --noproxy 127.0.0.1,localhost --silent --show-error --fail --max-time 0.2 "$url" 2>"$err")"; then
        rm -f "$err"
        return 1
    fi
    rm -f "$err"

    [[ "$body" == *"<title>Codex</title>"* ]] || return 1
    [[ "$body" == *"startup-loader"* ]] || return 1
}

webview_port_is_open() {
    # bash /dev/tcp connect, capped at 0.2 s by a watchdog kill.
    # Replaces python3 socket.connect heredoc (~3 ms vs 13–60 ms here).
    # /usr/bin/timeout is intentionally not used: on uutils-coreutils
    # distros (Ubuntu 26+) its cold start is ~105 ms, which would dominate
    # the 200 ms cap it would enforce.
    local probe_pid kill_pid rc=1 self_pid=$$
    ( trap - EXIT
      exec 3<>/dev/tcp/127.0.0.1/"$CODEX_LINUX_WEBVIEW_PORT" || exit 1
      exec 3>&- 3<&-
      exit 0 ) 2>/dev/null &
    probe_pid=$!
    # Watchdog: before SIGKILL, verify /proc/<probe>/stat still reports us as
    # the parent. Guards against PID reuse: if the probe has already exited
    # and the kernel has recycled that PID to an unrelated process between
    # the wait below and the watchdog firing, its ppid will not match
    # $self_pid and the SIGKILL is suppressed.
    ( trap - EXIT
      sleep 0.2
      [ -r "/proc/$probe_pid/stat" ] || exit 0
      # /proc/<pid>/stat: "<pid> (comm) <state> <ppid> ..." — comm can contain
      # spaces/parens, so strip up to the last ") " before splitting fields.
      local stat_line rest
      stat_line=$(< "/proc/$probe_pid/stat") || exit 0
      rest=${stat_line##*) }
      set -- $rest
      # $1 = state, $2 = ppid
      [ "${2:-}" = "$self_pid" ] && kill -9 "$probe_pid" 2>/dev/null
    ) 2>/dev/null &
    kill_pid=$!
    if wait "$probe_pid" 2>/dev/null; then rc=0; fi
    kill "$kill_pid" 2>/dev/null
    wait "$kill_pid" 2>/dev/null
    return $rc
}

wait_for_webview_server() {
    echo "Waiting for webview server on :$CODEX_LINUX_WEBVIEW_PORT"

    # Treat the actual localhost webview response as the readiness signal.
    # Under cold-login load the shell /dev/tcp probe can be a little too brittle,
    # while a short curl against index.html matches what Electron needs next.
    local attempt
    for attempt in $(seq 1 20); do
        if webview_origin_is_reachable_fast; then
            echo "Webview server is ready"
            return 0
        fi
        sleep 0.05
    done

    # The fast probe caps curl at 0.2s; a valid local webview can answer a little
    # slower during cold start. Confirm with the full 2s origin check before failing.
    if webview_origin_is_reachable; then
        echo "Webview server is ready"
        return 0
    fi

    return 1
}

verify_webview_origin() {
    local url="$1"

    # curl with built-in --max-time replaces python3 urllib (~7 ms vs ~38 ms).
    # --fail rejects non-2xx; --max-time 2 mirrors urllib.urlopen(timeout=2).
    # --show-error + 2>$err captures curl's actual diagnostic so failures
    # surface the underlying cause (HTTP status, connect refused, timeout,
    # SSL error, etc.) instead of a generic "HTTP fetch failed", matching
    # what the original python3 urllib version exposed via its raised
    # exception text.
    # --noproxy keeps loopback validation pointed at the embedded webview
    # server even when the user's shell exports http_proxy/ALL_PROXY without a
    # localhost NO_PROXY exemption.
    local body err detail
    err=$(mktemp 2>/dev/null) || err="${TMPDIR:-/tmp}/codex-curl-err.$$"
    if ! body="$(curl --disable --noproxy 127.0.0.1,localhost --silent --show-error --fail --max-time 2 "$url" 2>"$err")"; then
        detail=$(< "$err" 2>/dev/null)
        rm -f "$err"
        echo "Webview origin validation failed for $url; ${detail:-curl exited non-zero with no diagnostic}" >&2
        return 1
    fi
    rm -f "$err"

    local missing=()
    [[ "$body" == *"<title>Codex</title>"* ]] || missing+=("<title>Codex</title>")
    [[ "$body" == *"startup-loader"* ]]       || missing+=("startup-loader")
    if [ ${#missing[@]} -gt 0 ]; then
        echo "Webview origin validation failed for $url; missing markers: ${missing[*]}" >&2
        return 1
    fi
}

pid_has_webview_server_cmdline() {
    local pid="$1"
    local cmdline

    [[ "$pid" =~ ^[0-9]+$ ]] || return 1
    [ -d "/proc/$pid" ] || return 1
    pid_is_current_user "$pid" || return 1
    cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)"
    [[ "$cmdline" == *"http.server $CODEX_LINUX_WEBVIEW_PORT"* || "$cmdline" == *"webview-server.py $CODEX_LINUX_WEBVIEW_PORT"* ]]
}

pid_is_webview_server() {
    local pid="$1"
    local cwd

    pid_has_webview_server_cmdline "$pid" || return 1
    cwd="$(readlink -f "/proc/$pid/cwd" 2>/dev/null || true)"
    [ "$cwd" = "$(canonical_path "$WEBVIEW_DIR")" ]
}

pid_is_stale_webview_server() {
    local pid="$1"
    local cwd
    local current_webview_dir
    local deleted_webview_dir

    pid_has_webview_server_cmdline "$pid" || return 1
    cwd="$(readlink -f "/proc/$pid/cwd" 2>/dev/null || true)"
    [ -n "$cwd" ] || return 1

    current_webview_dir="$(canonical_path "$WEBVIEW_DIR")"
    deleted_webview_dir="$current_webview_dir (deleted)"
    [ "$cwd" = "$deleted_webview_dir" ] && return 0

    [ "$cwd" != "$current_webview_dir" ]
}

stop_owned_webview_server() {
    local pid=""

    if [ -f "$WEBVIEW_PID_FILE" ]; then
        pid="$(cat "$WEBVIEW_PID_FILE" 2>/dev/null || true)"
    fi

    if running_app_is_active && [ -n "$pid" ] && pid_is_webview_server "$pid"; then
        echo "Preserving webview server pid=$pid owned by running Codex Desktop pid=$RUNNING_APP_PID"
        return 0
    fi

    if [ -n "$pid" ] && { pid_is_webview_server "$pid" || pid_is_stale_webview_server "$pid"; }; then
        echo "Stopping owned webview server pid=$pid"
        kill "$pid" 2>/dev/null || true
        for _ in $(seq 1 20); do
            kill -0 "$pid" 2>/dev/null || break
            sleep 0.05
        done
    fi

    rm -f "$WEBVIEW_PID_FILE"
}

owned_webview_server_pid() {
    local pid=""

    if [ -f "$WEBVIEW_PID_FILE" ]; then
        pid="$(cat "$WEBVIEW_PID_FILE" 2>/dev/null || true)"
    fi

    if [ -n "$pid" ] && pid_is_webview_server "$pid"; then
        echo "$pid"
        return 0
    fi

    if [ -n "$pid" ]; then
        rm -f "$WEBVIEW_PID_FILE"
    fi

    return 1
}

discover_webview_server_pid() {
    local proc_cmdline
    local pid

    for proc_cmdline in /proc/[0-9]*/cmdline; do
        [ -e "$proc_cmdline" ] || continue
        pid="${proc_cmdline#/proc/}"
        pid="${pid%/cmdline}"
        if pid_is_webview_server "$pid"; then
            echo "$pid"
            return 0
        fi
    done

    return 1
}

stale_webview_server_pid() {
    local proc_cmdline
    local pid

    for proc_cmdline in /proc/[0-9]*/cmdline; do
        [ -e "$proc_cmdline" ] || continue
        pid="${proc_cmdline#/proc/}"
        pid="${pid%/cmdline}"
        if pid_is_stale_webview_server "$pid"; then
            echo "$pid"
            return 0
        fi
    done

    return 1
}

stop_stale_webview_server() {
    local pid

    running_app_is_active && return 0

    while pid="$(stale_webview_server_pid)"; do
        echo "Stopping stale webview server pid=$pid cwd=$(readlink -f "/proc/$pid/cwd" 2>/dev/null || true)"
        kill "$pid" 2>/dev/null || true
        for _ in $(seq 1 20); do
            kill -0 "$pid" 2>/dev/null || break
            sleep 0.05
        done
    done
}

adopt_existing_webview_server() {
    local pid

    if pid="$(owned_webview_server_pid)"; then
        if running_app_is_active; then
            ADOPTED_WEBVIEW_PID="$pid"
            echo "Reusing webview server pid=$pid owned by running Codex Desktop pid=$RUNNING_APP_PID"
        else
            STARTED_WEBVIEW_PID="$pid"
        fi
        return 0
    fi

    return 1
}

adopt_discovered_webview_server() {
    local pid

    if pid="$(discover_webview_server_pid)"; then
        echo "$pid" > "$WEBVIEW_PID_FILE"
        if running_app_is_active; then
            ADOPTED_WEBVIEW_PID="$pid"
            echo "Reusing webview server pid=$pid owned by running Codex Desktop pid=$RUNNING_APP_PID"
        else
            STARTED_WEBVIEW_PID="$pid"
            echo "Adopted existing webview server pid=$pid dir=$WEBVIEW_DIR"
        fi
        return 0
    fi

    return 1
}

ensure_webview_server() {
    if [ ! -d "$WEBVIEW_DIR" ] || [ ! "$(ls -A "$WEBVIEW_DIR" 2>/dev/null)" ]; then
        return 0
    fi

    if adopt_existing_webview_server; then
        if webview_origin_is_reachable; then
            echo "Reusing existing verified webview server on :$CODEX_LINUX_WEBVIEW_PORT"
            log_phase "webview_reused"
            return 0
        fi

        if running_app_is_active; then
            notify_error "Codex Desktop webview server is already running for pid $RUNNING_APP_PID, but origin validation failed. Keeping the live app untouched."
            exit 1
        fi
    fi

    if webview_port_is_open; then
        stop_stale_webview_server
    fi

    if webview_port_is_open && webview_origin_is_reachable; then
        if adopt_discovered_webview_server; then
            echo "Reusing existing verified webview server on :$CODEX_LINUX_WEBVIEW_PORT"
            log_phase "webview_reused"
            return 0
        fi

        notify_error "$CODEX_LINUX_APP_DISPLAY_NAME webview port $CODEX_LINUX_WEBVIEW_PORT is already serving Codex content, but it is not owned by this launcher. Stop the other webview server and try again."
        exit 1
    fi

    if webview_port_is_open; then
        stop_stale_webview_server
        if webview_origin_is_reachable; then
            if adopt_discovered_webview_server; then
                echo "Reusing existing verified webview server on :$CODEX_LINUX_WEBVIEW_PORT"
                log_phase "webview_reused"
                return 0
            fi
        fi

        if webview_port_is_open; then
            notify_error "$CODEX_LINUX_APP_DISPLAY_NAME webview port $CODEX_LINUX_WEBVIEW_PORT is already in use. Stop the other process and try again."
            exit 1
        fi
    fi

    stop_owned_webview_server

    cd "$WEBVIEW_DIR"
    python3 "$SCRIPT_DIR/.codex-linux/webview-server.py" "$CODEX_LINUX_WEBVIEW_PORT" --bind 127.0.0.1 &
    STARTED_WEBVIEW_PID=$!
    echo "$STARTED_WEBVIEW_PID" > "$WEBVIEW_PID_FILE"

    echo "Started webview server pid=$STARTED_WEBVIEW_PID dir=$WEBVIEW_DIR"

    if ! wait_for_webview_server; then
        notify_error "$CODEX_LINUX_APP_DISPLAY_NAME webview server did not become ready on port $CODEX_LINUX_WEBVIEW_PORT. Check the launcher log for the embedded http.server output."
        exit 1
    fi

    if ! kill -0 "$STARTED_WEBVIEW_PID" 2>/dev/null; then
        if webview_origin_is_reachable && adopt_discovered_webview_server; then
            echo "Adopted concurrently-started verified webview server on :$CODEX_LINUX_WEBVIEW_PORT"
            log_phase "webview_reused"
            return 0
        fi

        notify_error "$CODEX_LINUX_APP_DISPLAY_NAME webview server exited before Electron launch. Another process may already be using port $CODEX_LINUX_WEBVIEW_PORT."
        exit 1
    fi

    if ! verify_webview_origin "$WEBVIEW_ORIGIN/index.html"; then
        notify_error "$CODEX_LINUX_APP_DISPLAY_NAME webview origin validation failed. Another process may be serving port $CODEX_LINUX_WEBVIEW_PORT or the extracted webview bundle is incomplete."
        exit 1
    fi

    echo "Webview origin verified."
    log_phase "webview_ready"
}

clear_stale_pid_file() {
    if [ ! -f "$APP_PID_FILE" ]; then
        return 0
    fi

    local pid=""
    pid="$(cat "$APP_PID_FILE" 2>/dev/null || true)"
    if [ -z "$pid" ] || ! pid_matches_executable "$pid" "$SCRIPT_DIR/electron"; then
        rm -f "$APP_PID_FILE"
    fi
}

reconcile_runtime_state() {
    local live_app_pid=""
    local webview_pid=""

    if live_app_pid="$(find_running_app_pid)" || { [ -S "$LAUNCH_ACTION_SOCKET" ] && live_app_pid="$(discover_running_app_pid)"; }; then
        echo "$live_app_pid" > "$APP_PID_FILE"
        return 0
    fi

    clear_stale_pid_file

    if [ -e "$LAUNCH_ACTION_SOCKET" ]; then
        rm -f "$LAUNCH_ACTION_SOCKET"
    fi

    if [ ! -f "$WEBVIEW_PID_FILE" ]; then
        return 0
    fi

    webview_pid="$(cat "$WEBVIEW_PID_FILE" 2>/dev/null || true)"
    if [ -z "$webview_pid" ] || { ! pid_is_webview_server "$webview_pid" && ! pid_is_stale_webview_server "$webview_pid"; }; then
        rm -f "$WEBVIEW_PID_FILE"
    fi
}

is_wsl_environment() {
    if [ -n "${WSL_INTEROP:-}" ] || [ -n "${WSL_DISTRO_NAME:-}" ]; then
        return 0
    fi

    grep -qiE "(microsoft|wsl)" /proc/sys/kernel/osrelease 2>/dev/null
}

is_wslg_session() {
    is_wsl_environment || return 1

    if [ -n "${WAYLAND_DISPLAY:-}" ]; then
        return 0
    fi

    [ -n "${DISPLAY:-}" ] && [ -e /mnt/wslg ]
}

normalize_linux_rendering_mode() {
    case "${CODEX_LINUX_RENDERING_MODE:-auto}" in
        auto|default|wslg|wayland-gpu)
            echo "${CODEX_LINUX_RENDERING_MODE:-auto}"
            ;;
        *)
            echo "Invalid CODEX_LINUX_RENDERING_MODE='${CODEX_LINUX_RENDERING_MODE:-}'; using auto" >&2
            echo "auto"
            ;;
    esac
}

truthy_env_value() {
    case "${1:-}" in
        1|true|TRUE|yes|YES|on|ON) return 0 ;;
        *) return 1 ;;
    esac
}

falsey_env_value() {
    case "${1:-}" in
        0|false|FALSE|no|NO|off|OFF) return 0 ;;
        *) return 1 ;;
    esac
}

scan_electron_rendering_arg() {
    case "$1" in
        --ozone-platform=*)
            ELECTRON_OZONE_SWITCH_IN_ARGS=1
            ELECTRON_OZONE_PLATFORM_ARG="${1#--ozone-platform=}"
            ;;
        --ozone-platform|--ozone-platform-hint|--ozone-platform-hint=*)
            ELECTRON_OZONE_SWITCH_IN_ARGS=1
            ;;
        --use-gl|--use-gl=*|--use-angle|--use-angle=*)
            ELECTRON_GL_SWITCH_PROVIDED=1
            ;;
        --disable-gpu|--disable-gpu=*)
            ELECTRON_GPU_DISABLE_SWITCH_IN_ARGS=1
            ;;
        --disable-gpu-compositing|--disable-gpu-compositing=*)
            ELECTRON_GPU_COMPOSITING_SWITCH_PROVIDED=1
            ;;
    esac
}

write_user_electron_flags_template() {
    cat > "$USER_ELECTRON_FLAGS_FILE" <<'EOF'
# Codex Desktop Linux launch flags.
# Uncomment one flag per line. Existing files are never overwritten.
# Lines starting with # and blank lines are ignored.
# Shell syntax, quotes, variables, and inline comments are not evaluated.
#
# X11 / Wayland:
# --x11
# --wayland
# --ozone-platform=x11
# --ozone-platform=wayland
# --ozone-platform-hint=auto
#
# Wayland / IME:
# --enable-wayland-ime
# --wayland-text-input-version=1
#
# GPU / rendering:
# --safe-mode
# --disable-gpu
# --enable-gpu
# --disable-gpu-compositing
# --use-gl=angle
# --use-gl=desktop
# --use-angle=gl
# --disable-features=Vulkan
#
# Accessibility:
# --force-renderer-accessibility
#
# Wayland decorations:
# --enable-features=WaylandWindowDecorations
EOF
}

ensure_user_electron_flags_file() {
    [ -n "${USER_ELECTRON_FLAGS_FILE:-}" ] || return 0
    if [ -e "$USER_ELECTRON_FLAGS_FILE" ] || [ -L "$USER_ELECTRON_FLAGS_FILE" ]; then
        return 0
    fi

    mkdir -p "$(dirname "$USER_ELECTRON_FLAGS_FILE")"
    if write_user_electron_flags_template; then
        echo "Created persistent Electron flags template: $USER_ELECTRON_FLAGS_FILE"
    else
        echo "Could not create persistent Electron flags template: $USER_ELECTRON_FLAGS_FILE"
        rm -f "$USER_ELECTRON_FLAGS_FILE" 2>/dev/null || true
    fi
}

load_user_electron_flags() {
    USER_ELECTRON_FLAGS=()
    [ -n "${USER_ELECTRON_FLAGS_FILE:-}" ] || return 0
    [ -f "$USER_ELECTRON_FLAGS_FILE" ] || return 0

    local line
    while IFS= read -r line || [ -n "$line" ]; do
        line="${line%$'\r'}"
        case "$line" in
            ""|\#*) continue ;;
            --)
                echo "Ignoring unsupported separator in Electron flags file: $USER_ELECTRON_FLAGS_FILE"
                continue
                ;;
        esac
        USER_ELECTRON_FLAGS+=("$line")
    done < "$USER_ELECTRON_FLAGS_FILE"

    if [ "${#USER_ELECTRON_FLAGS[@]}" -gt 0 ]; then
        echo "Loaded ${#USER_ELECTRON_FLAGS[@]} persistent Electron flag(s): $USER_ELECTRON_FLAGS_FILE"
    fi
}

apply_electron_rendering_profile() {
    local requested_mode
    requested_mode="$(normalize_linux_rendering_mode)"

    ELECTRON_RENDERING_MODE="$requested_mode"
    ELECTRON_WSLG_DETECTED=0
    if is_wslg_session; then
        ELECTRON_WSLG_DETECTED=1
    fi

    case "$requested_mode" in
        auto)
            if [ "$ELECTRON_WSLG_DETECTED" -eq 1 ]; then
                ELECTRON_RENDERING_MODE="wslg"
            else
                ELECTRON_RENDERING_MODE="default"
            fi
            ;;
        default)
            return 0
            ;;
        wayland-gpu)
            if [ "$ELECTRON_PLATFORM_EXPLICIT" -eq 0 ] && [ "$ELECTRON_OZONE_SWITCH_IN_ARGS" -eq 0 ]; then
                ELECTRON_OZONE_PLATFORM="wayland"
                ELECTRON_OZONE_HINT=""
            fi
            return 0
            ;;
    esac

    [ "$ELECTRON_RENDERING_MODE" = "wslg" ] || return 0

    if [ "$ELECTRON_PLATFORM_EXPLICIT" -eq 0 ] && [ "$ELECTRON_OZONE_SWITCH_IN_ARGS" -eq 0 ]; then
        ELECTRON_OZONE_PLATFORM="x11"
        ELECTRON_OZONE_HINT=""
    fi

    if [ "$ELECTRON_GL_SWITCH_PROVIDED" -eq 0 ] && [ "$ELECTRON_GPU_ENABLED" = "1" ] && [ "$ELECTRON_GPU_DISABLE_SWITCH_IN_ARGS" -eq 0 ]; then
        ELECTRON_ARGS+=(--use-gl=angle)
        ELECTRON_GL_SWITCH_PROVIDED=1
        ELECTRON_GL_SWITCH_ADDED=1
    fi
}

should_disable_gpu_compositing() {
    if [ "$ELECTRON_GPU_COMPOSITING_SWITCH_PROVIDED" -eq 1 ]; then
        return 0
    fi

    if [ -n "${CODEX_ELECTRON_DISABLE_GPU_COMPOSITING:-}" ]; then
        if truthy_env_value "$CODEX_ELECTRON_DISABLE_GPU_COMPOSITING"; then
            return 0
        fi
        if falsey_env_value "$CODEX_ELECTRON_DISABLE_GPU_COMPOSITING"; then
            return 1
        fi
        echo "Invalid CODEX_ELECTRON_DISABLE_GPU_COMPOSITING='${CODEX_ELECTRON_DISABLE_GPU_COMPOSITING:-}'; using rendering profile default" >&2
    fi

    return 1
}

effective_electron_ozone_platform() {
    if [ -n "$ELECTRON_OZONE_PLATFORM" ]; then
        echo "$ELECTRON_OZONE_PLATFORM"
        return 0
    fi

    if [ -n "$ELECTRON_OZONE_PLATFORM_ARG" ]; then
        echo "$ELECTRON_OZONE_PLATFORM_ARG"
    fi
}

should_force_renderer_accessibility() {
    local effective_platform

    if [ -n "${CODEX_FORCE_RENDERER_ACCESSIBILITY:-}" ]; then
        if truthy_env_value "$CODEX_FORCE_RENDERER_ACCESSIBILITY"; then
            return 0
        fi
        if falsey_env_value "$CODEX_FORCE_RENDERER_ACCESSIBILITY"; then
            return 1
        fi
        case "$CODEX_FORCE_RENDERER_ACCESSIBILITY" in
            auto|AUTO|Auto) ;;
            *)
                echo "Invalid CODEX_FORCE_RENDERER_ACCESSIBILITY='${CODEX_FORCE_RENDERER_ACCESSIBILITY:-}'; using rendering profile default" >&2
                ;;
        esac
    fi

    case "$ELECTRON_RENDERING_MODE" in
        wslg)
            return 1
            ;;
        wayland-gpu)
            effective_platform="$(effective_electron_ozone_platform)"
            [ "$effective_platform" = "wayland" ] && return 1
            return 0
            ;;
    esac

    return 0
}

set_electron_defaults() {
    ELECTRON_OZONE_PLATFORM=""
    ELECTRON_OZONE_HINT="auto"
    ELECTRON_GPU_ENABLED=1
    ELECTRON_RENDERING_MODE="default"
    ELECTRON_WSLG_DETECTED=0
    ELECTRON_PLATFORM_EXPLICIT=0
    ELECTRON_OZONE_SWITCH_IN_ARGS=0
    ELECTRON_OZONE_PLATFORM_ARG=""
    ELECTRON_GL_SWITCH_PROVIDED=0
    ELECTRON_GL_SWITCH_ADDED=0
    ELECTRON_GPU_DISABLE_SWITCH_IN_ARGS=0
    ELECTRON_GPU_COMPOSITING_DISABLED=0
    ELECTRON_GPU_COMPOSITING_SWITCH_PROVIDED=0
    ELECTRON_RENDERER_ACCESSIBILITY_FORCED=0
    ELECTRON_ARGS=()
    local passthrough=0

    while [ "$#" -gt 0 ]; do
        if [ "$passthrough" -eq 1 ]; then
            ELECTRON_ARGS+=("$1")
            scan_electron_rendering_arg "$1"
            shift
            continue
        fi

        case "$1" in
            --)
                passthrough=1
                ;;
            --safe-mode)
                ELECTRON_OZONE_PLATFORM="x11"
                ELECTRON_OZONE_HINT=""
                ELECTRON_GPU_ENABLED=0
                ELECTRON_PLATFORM_EXPLICIT=1
                ;;
            --disable-gpu)
                ELECTRON_GPU_ENABLED=0
                ;;
            --enable-gpu)
                ELECTRON_GPU_ENABLED=1
                ;;
            --x11)
                ELECTRON_OZONE_PLATFORM="x11"
                ELECTRON_OZONE_HINT=""
                ELECTRON_PLATFORM_EXPLICIT=1
                ;;
            --wayland)
                ELECTRON_OZONE_PLATFORM="wayland"
                ELECTRON_OZONE_HINT=""
                ELECTRON_PLATFORM_EXPLICIT=1
                ;;
            --ozone-platform=*)
                ELECTRON_OZONE_PLATFORM="${1#--ozone-platform=}"
                ELECTRON_OZONE_HINT=""
                ELECTRON_PLATFORM_EXPLICIT=1
                ;;
            --ozone-platform-hint=*)
                ELECTRON_OZONE_HINT="${1#--ozone-platform-hint=}"
                ELECTRON_OZONE_PLATFORM=""
                ELECTRON_PLATFORM_EXPLICIT=1
                ;;
            *)
                ELECTRON_ARGS+=("$1")
                scan_electron_rendering_arg "$1"
                ;;
        esac
        shift
    done

    # ChromeOS / Crostini (Sommelier) auto-fallback to X11.
    #
    # On a Crostini Linux container the default Wayland session is provided by
    # Sommelier (see SOMMELIER_VERSION/SOMMELIER_VM_IDENTIFIER, exported by
    # cros-garcon for both terminal and app-menu launches). With the default
    # Electron path (--ozone-platform-hint=auto) Electron picks the Wayland
    # backend, reaches `window ready-to-show`, but the window never surfaces in
    # the ChromeOS shell — leaving the launcher icon spinning indefinitely with
    # no visible app. Forcing X11 (proxied by sommelier-x to ChromeOS) is the
    # documented workaround. Skip this only when the operator explicitly chose
    # a platform via --x11 / --wayland / --ozone-platform=*.
    #
    # See https://github.com/ilysenko/codex-desktop-linux/issues/95.
    if [ -z "$ELECTRON_OZONE_PLATFORM" ] \
        && [ "$ELECTRON_OZONE_HINT" = "auto" ] \
        && [ -n "${SOMMELIER_VERSION:-}" ]; then
        ELECTRON_OZONE_PLATFORM="x11"
        ELECTRON_OZONE_HINT=""
        echo "Detected Sommelier (SOMMELIER_VERSION=$SOMMELIER_VERSION); forcing --ozone-platform=x11 (Wayland windows are not visible under ChromeOS Crostini)"
    fi

    apply_electron_rendering_profile
}

load_feature_electron_args() {
    FEATURE_ELECTRON_ARGS=()
    [ -n "${FEATURE_ELECTRON_ARGS_DIR:-}" ] || return 0
    [ -d "$FEATURE_ELECTRON_ARGS_DIR" ] || return 0

    local args_file
    local line
    for args_file in "$FEATURE_ELECTRON_ARGS_DIR"/*; do
        [ -f "$args_file" ] || continue
        echo "Loading Linux feature Electron args: $args_file"
        while IFS= read -r line || [ -n "$line" ]; do
            line="${line%$'\r'}"
            case "$line" in
                ""|\#*) continue ;;
            esac
            FEATURE_ELECTRON_ARGS+=("$line")
        done < "$args_file"
    done
}

build_electron_launch_args() {
    ELECTRON_LAUNCH_ARGS=(
        --no-sandbox
        --class="$CODEX_LINUX_APP_ID"
        --app-id="$CODEX_LINUX_APP_ID"
        --disable-dev-shm-usage
        --disable-gpu-sandbox
    )

    if should_disable_gpu_compositing; then
        ELECTRON_LAUNCH_ARGS+=(--disable-gpu-compositing)
        ELECTRON_GPU_COMPOSITING_DISABLED=1
    else
        ELECTRON_GPU_COMPOSITING_DISABLED=0
    fi

    if [ "$CODEX_LINUX_APP_ID" != "codex-desktop" ]; then
        ELECTRON_LAUNCH_ARGS+=(--user-data-dir="${CODEX_ELECTRON_USER_DATA_DIR:-$APP_STATE_DIR/electron-user-data}")
    elif [ -n "${CODEX_ELECTRON_USER_DATA_DIR:-}" ]; then
        ELECTRON_LAUNCH_ARGS+=(--user-data-dir="$CODEX_ELECTRON_USER_DATA_DIR")
    fi

    if [ -n "$ELECTRON_OZONE_PLATFORM" ]; then
        ELECTRON_LAUNCH_ARGS+=(--ozone-platform="$ELECTRON_OZONE_PLATFORM")
    elif [ -n "$ELECTRON_OZONE_HINT" ] && [ "$ELECTRON_OZONE_SWITCH_IN_ARGS" -eq 0 ]; then
        ELECTRON_LAUNCH_ARGS+=(--ozone-platform-hint="$ELECTRON_OZONE_HINT")
    fi

    if [ "$ELECTRON_GPU_ENABLED" != "1" ]; then
        ELECTRON_LAUNCH_ARGS+=(--disable-gpu --disable-features=Vulkan)
    fi

    if should_force_renderer_accessibility; then
        ELECTRON_LAUNCH_ARGS+=(--force-renderer-accessibility)
        ELECTRON_RENDERER_ACCESSIBILITY_FORCED=1
    else
        ELECTRON_RENDERER_ACCESSIBILITY_FORCED=0
    fi

    if [ "$ELECTRON_OZONE_PLATFORM" = "wayland" ]; then
        ELECTRON_LAUNCH_ARGS+=(--enable-features=WaylandWindowDecorations)
    fi

}

configure_side_by_side_app_env() {
    if [ "$CODEX_LINUX_APP_ID" = "codex-desktop" ]; then
        return 0
    fi

    XDG_CONFIG_HOME="${CODEX_XDG_CONFIG_HOME:-$APP_STATE_DIR/xdg-config}"
    CODEX_ELECTRON_USER_DATA_DIR="${CODEX_ELECTRON_USER_DATA_DIR:-$APP_STATE_DIR/electron-user-data}"
    export XDG_CONFIG_HOME CODEX_ELECTRON_USER_DATA_DIR
}

cleanup_launcher() {
    if [ -n "${ELECTRON_PID:-}" ] && [ -f "$APP_PID_FILE" ]; then
        local current_pid
        current_pid="$(cat "$APP_PID_FILE" 2>/dev/null || true)"
        if [ "$current_pid" = "$ELECTRON_PID" ]; then
            rm -f "$APP_PID_FILE"
        fi
    fi

    if [ -n "${STARTED_WEBVIEW_PID:-}" ] && pid_is_webview_server "$STARTED_WEBVIEW_PID"; then
        kill "$STARTED_WEBVIEW_PID" 2>/dev/null || true
        rm -f "$WEBVIEW_PID_FILE"
    fi
}

launch_electron() {
    cd "$SCRIPT_DIR"
    log_phase "electron_launch"

    ensure_user_electron_flags_file
    load_feature_electron_args
    load_user_electron_flags
    set_electron_defaults "${FEATURE_ELECTRON_ARGS[@]}" "${USER_ELECTRON_FLAGS[@]}" "$@"
    build_electron_launch_args

    if [ "$WARM_START" -eq 1 ]; then
        echo "Electron warm-start handoff: pid=$RUNNING_APP_PID rendering_mode=$ELECTRON_RENDERING_MODE wslg_detected=$ELECTRON_WSLG_DETECTED ozone_platform=${ELECTRON_OZONE_PLATFORM:-default} ozone_hint=${ELECTRON_OZONE_HINT:-none} gpu_enabled=$ELECTRON_GPU_ENABLED gpu_disable_arg=$ELECTRON_GPU_DISABLE_SWITCH_IN_ARGS gpu_compositing_disabled=$ELECTRON_GPU_COMPOSITING_DISABLED gl_switch_added=$ELECTRON_GL_SWITCH_ADDED renderer_accessibility_forced=$ELECTRON_RENDERER_ACCESSIBILITY_FORCED"
        unset ELECTRON_RUN_AS_NODE
        "$SCRIPT_DIR/electron" "${ELECTRON_LAUNCH_ARGS[@]}" "${ELECTRON_ARGS[@]}"
        return $?
    fi

    echo "Electron launch mode: rendering_mode=$ELECTRON_RENDERING_MODE wslg_detected=$ELECTRON_WSLG_DETECTED ozone_platform=${ELECTRON_OZONE_PLATFORM:-default} ozone_hint=${ELECTRON_OZONE_HINT:-none} gpu_enabled=$ELECTRON_GPU_ENABLED gpu_disable_arg=$ELECTRON_GPU_DISABLE_SWITCH_IN_ARGS gpu_compositing_disabled=$ELECTRON_GPU_COMPOSITING_DISABLED gl_switch_added=$ELECTRON_GL_SWITCH_ADDED renderer_accessibility_forced=$ELECTRON_RENDERER_ACCESSIBILITY_FORCED"
    unset ELECTRON_RUN_AS_NODE
    "$SCRIPT_DIR/electron" "${ELECTRON_LAUNCH_ARGS[@]}" "${ELECTRON_ARGS[@]}" &
    ELECTRON_PID=$!
    if [ -n "${RUNNING_APP_PID:-}" ] && pid_matches_executable "$RUNNING_APP_PID" "$SCRIPT_DIR/electron"; then
        echo "Preserving Codex Desktop pid=$RUNNING_APP_PID liveness marker for second-instance handoff"
    else
        echo "$ELECTRON_PID" > "$APP_PID_FILE"
    fi
    log_phase "electron_spawned"

    set +e
    wait "$ELECTRON_PID"
    local status=$?
    set -e
    run_feature_after_exit_hooks "$status"
    return "$status"
}

hydrate_graphical_session_env
configure_side_by_side_app_env
export_feature_runtime_env
load_packaged_runtime_helper
prepend_managed_node_runtime_to_path
source_feature_env_files
register_url_scheme_handlers
reconcile_runtime_state
detect_warm_start
trap cleanup_launcher EXIT

if send_warm_start_launch_action "${LAUNCHER_ARGS[@]}"; then
    echo "Sent launch args over warm-start IPC"
    log_phase "warm_start_ipc_sent"
    exit 0
elif [ "$WARM_START" -eq 1 ]; then
    echo "Warm-start IPC unavailable; falling back to Electron second-instance handoff"
fi

if using_second_instance_handoff; then
    echo "Detected running Codex Desktop pid=$RUNNING_APP_PID; using Electron second-instance handoff"
    log_phase "second_instance_handoff_ready"
elif needs_cold_start; then
    run_feature_prelaunch_hooks
    log_phase "feature_prelaunch"
    run_packaged_runtime_prelaunch
    log_phase "packaged_prelaunch"
    ensure_webview_server
else
    echo "Skipping packaged prelaunch and webview setup for warm start"
    log_phase "warm_start_ready"
fi

if needs_cold_start && [ -z "${CODEX_CLI_PATH:-}" ]; then
    CODEX_CLI_PATH="$(find_codex_cli || true)"
    export CODEX_CLI_PATH
    log_phase "cli_lookup"
fi
export CHROME_DESKTOP="${CHROME_DESKTOP:-$CODEX_LINUX_APP_ID.desktop}"
if truthy_env_value "${CODEX_LINUX_PIN_RENDERER_URL:-}" && ! truthy_env_value "${CODEX_LINUX_ALLOW_RENDERER_URL_OVERRIDE:-}"; then
    if [ -n "${ELECTRON_RENDERER_URL:-}" ] && [ "$ELECTRON_RENDERER_URL" != "$WEBVIEW_ORIGIN/" ]; then
        echo "Ignoring inherited ELECTRON_RENDERER_URL; set CODEX_LINUX_ALLOW_RENDERER_URL_OVERRIDE=1 to allow overrides"
    fi
    export ELECTRON_RENDERER_URL="$WEBVIEW_ORIGIN/"
else
    export ELECTRON_RENDERER_URL="${ELECTRON_RENDERER_URL:-$WEBVIEW_ORIGIN/}"
fi

if needs_cold_start && [ -z "$CODEX_CLI_PATH" ]; then
    if is_interactive_terminal; then
        if prompt_install_missing_cli; then
            if ! run_cli_preflight 1; then
                notify_error "Codex CLI installation was attempted but did not succeed. Retry by reopening the app, or install it manually with: npm i -g @openai/codex or npm i -g --prefix ~/.local @openai/codex"
                exit 1
            fi
        fi
    elif ! run_gui_cli_prompt; then
        notify_error "Codex CLI is required but is not installed. Reopen the app to retry the automatic install flow, or install it manually with: npm i -g @openai/codex or npm i -g --prefix ~/.local @openai/codex"
        exit 1
    fi
fi

if needs_cold_start && [ -z "$CODEX_CLI_PATH" ]; then
    notify_error "Codex CLI is required but is not installed. Reopen the app to retry the automatic install flow, or install it manually with: npm i -g @openai/codex or npm i -g --prefix ~/.local @openai/codex"
    exit 1
fi

if needs_cold_start; then
    if [ "${CODEX_SYNC_CLI_PREFLIGHT:-0}" = "1" ]; then
        run_cli_preflight 0
        log_phase "cli_preflight_sync"
    else
        run_cli_preflight_background
        log_phase "cli_preflight_backgrounded"
    fi
fi

export_packaged_runtime_env
if needs_cold_start; then
    clear_bundled_marketplace_tmp_cache
    # The runtime marketplace is populated asynchronously and can briefly
    # recreate read-only plugin cache trees before the sync below replaces them.
    monitor_bundled_marketplace_tmp_permissions
    sync_browser_use_bundled_plugin_cache
    sync_chrome_bundled_plugin_cache
    sync_computer_use_bundled_plugin_cache
    sync_read_aloud_bundled_plugin_cache
    run_cold_start_hooks
fi
resolve_browser_use_runtime_env

echo "Using CODEX_CLI_PATH=${CODEX_CLI_PATH:-warm-start-skip}"
echo "Using managed Node.js runtime=${CODEX_MANAGED_NODE_RUNTIME_DIR:-$SCRIPT_DIR/resources/node-runtime}"

launch_electron "${LAUNCHER_ARGS[@]}"
