fix: default provider policy, anti-stall self-kill, _schema NameError (cobra PR #17)
This commit is contained in:
@@ -41,7 +41,7 @@ from codex_launcher_lib import (
|
||||
recover_config_if_needed, write_config_for_native, write_config_for_translated,
|
||||
endpoint_models_url, endpoint_model_headers, fetch_models_for_endpoint,
|
||||
refresh_endpoint_models, run_endpoint_doctor,
|
||||
detect_codex_cli, detect_codex_desktop, check_codex_auth,
|
||||
detect_codex_cli, detect_codex_desktop, launch_codex_desktop, is_codex_desktop_running, check_codex_auth,
|
||||
last_log_lines, kill_existing_desktop, safe_cleanup_owned,
|
||||
start_proxy_for, stop_proxy, start_bgp_proxy, get_proxy_state, set_proxy_state,
|
||||
detect_terminal, open_url, open_file, write_secure_text,
|
||||
@@ -2338,9 +2338,10 @@ class LauncherWin:
|
||||
|
||||
desk_row = ttk.Frame(main)
|
||||
desk_row.pack(fill="x", pady=(2, 0))
|
||||
if self._desktop_info:
|
||||
if self._desktop_info[0]:
|
||||
label = "MSIX (Store)" if self._desktop_info[1] else self._desktop_info[0]
|
||||
ttk.Label(desk_row, text="✓ Codex Desktop", foreground="#2ea043").pack(side="left")
|
||||
ttk.Label(desk_row, text=f" ({self._desktop_info})", foreground="gray").pack(side="left")
|
||||
ttk.Label(desk_row, text=f" ({label})", foreground="gray").pack(side="left")
|
||||
else:
|
||||
ttk.Label(desk_row, text="✗ Codex Desktop -- not found", foreground="#d29922").pack(side="left")
|
||||
ttk.Button(desk_row, text="Install", command=lambda: self._show_install_guide("desktop")).pack(side="left", padx=(6, 0))
|
||||
@@ -2348,7 +2349,7 @@ class LauncherWin:
|
||||
self._missing = []
|
||||
if not self._cli_info:
|
||||
self._missing.append("cli")
|
||||
if not self._desktop_info:
|
||||
if not self._desktop_info[0]:
|
||||
self._missing.append("desktop")
|
||||
|
||||
# Auth status
|
||||
@@ -2461,8 +2462,9 @@ class LauncherWin:
|
||||
self.log(f"✓ Codex CLI detected ({ver})")
|
||||
else:
|
||||
self.log("✗ Codex CLI NOT found -- CLI launch disabled.")
|
||||
if self._desktop_info:
|
||||
self.log(f"✓ Codex Desktop detected ({self._desktop_info})")
|
||||
if self._desktop_info[0]:
|
||||
label = "MSIX (Store)" if self._desktop_info[1] else self._desktop_info[0]
|
||||
self.log(f"✓ Codex Desktop detected ({label})")
|
||||
else:
|
||||
self.log("✗ Codex Desktop NOT found -- Desktop launch disabled.")
|
||||
if self._missing:
|
||||
@@ -3132,7 +3134,7 @@ class LauncherWin:
|
||||
|
||||
# ── Launch ───────────────────────────────────────────────────────
|
||||
|
||||
def _set_busy(self, busy):
|
||||
def _set_busy(self, busy, proxy_alive=False):
|
||||
has_cli = "cli" not in self._missing
|
||||
has_desk = "desktop" not in self._missing
|
||||
def _update():
|
||||
@@ -3140,8 +3142,8 @@ class LauncherWin:
|
||||
self._btn_cli.configure(state="disabled" if busy or not has_cli else "normal")
|
||||
self._btn_codex_desktop.configure(state="disabled" if busy or not has_desk else "normal")
|
||||
self._btn_codex_cli.configure(state="disabled" if busy or not has_cli else "normal")
|
||||
self._kill_btn.configure(state="normal" if busy else "disabled")
|
||||
self._restart_btn.configure(state="normal" if busy else "disabled")
|
||||
self._kill_btn.configure(state="normal" if busy or proxy_alive else "disabled")
|
||||
self._restart_btn.configure(state="normal" if busy or proxy_alive else "disabled")
|
||||
self._root.after(0, _update)
|
||||
|
||||
def _launch(self, target):
|
||||
@@ -3228,7 +3230,7 @@ class LauncherWin:
|
||||
finally:
|
||||
if keep_session_alive:
|
||||
self.log("Warm-start handoff detected; keeping proxy/config active for running Desktop.")
|
||||
self._set_busy(False)
|
||||
self._set_busy(False, proxy_alive=True)
|
||||
self.log("Ready. Use Kill && Cleanup when finished.")
|
||||
else:
|
||||
stop_proxy()
|
||||
@@ -3292,25 +3294,31 @@ class LauncherWin:
|
||||
self.log("Ready.")
|
||||
|
||||
def _launch_desktop(self, ep, model):
|
||||
desktop_path = self._desktop_info
|
||||
if not desktop_path:
|
||||
if not self._desktop_info[0]:
|
||||
self.log("ERROR: Codex Desktop not found")
|
||||
return False
|
||||
|
||||
if IS_WINDOWS:
|
||||
self._proc = subprocess.Popen(
|
||||
[desktop_path],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||
else:
|
||||
self._proc = subprocess.Popen(
|
||||
[desktop_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
preexec_fn=os.setsid)
|
||||
_, is_msix = self._desktop_info
|
||||
self._proc = launch_codex_desktop(self._desktop_info)
|
||||
if not self._proc:
|
||||
self.log("ERROR: Failed to launch Codex Desktop")
|
||||
return False
|
||||
|
||||
pid = self._proc.pid
|
||||
self.log(f"Desktop started (PID {pid})")
|
||||
self.log(f"Log: {LAUNCH_LOG}")
|
||||
|
||||
# MSIX: cmd.exe exits immediately, monitor via tasklist instead
|
||||
if is_msix and IS_WINDOWS:
|
||||
time.sleep(3)
|
||||
if not is_codex_desktop_running():
|
||||
self.log("ERROR: Codex Desktop did not start")
|
||||
self._proc = None
|
||||
return False
|
||||
self.log("Codex Desktop is running (MSIX)")
|
||||
self._proc = None
|
||||
return True
|
||||
|
||||
t0 = time.time()
|
||||
stall_warned = False
|
||||
while self._proc and self._proc.poll() is None:
|
||||
@@ -3366,18 +3374,13 @@ class LauncherWin:
|
||||
|
||||
def _launch_desktop_direct(self):
|
||||
self.log("Launching Codex Desktop (default OAuth)...")
|
||||
desktop_path = self._desktop_info
|
||||
if not desktop_path:
|
||||
if not self._desktop_info[0]:
|
||||
self.log("ERROR: Codex Desktop not found")
|
||||
return
|
||||
if IS_WINDOWS:
|
||||
self._proc = subprocess.Popen(
|
||||
[desktop_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||
else:
|
||||
self._proc = subprocess.Popen(
|
||||
[desktop_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
preexec_fn=os.setsid)
|
||||
self._proc = launch_codex_desktop(self._desktop_info)
|
||||
if not self._proc:
|
||||
self.log("ERROR: Failed to launch Codex Desktop")
|
||||
return
|
||||
pid = self._proc.pid
|
||||
self.log(f"Desktop started (PID {pid})")
|
||||
|
||||
|
||||
@@ -1729,6 +1729,12 @@ def detect_codex_cli():
|
||||
|
||||
|
||||
def detect_codex_desktop():
|
||||
"""Detect Codex Desktop installation.
|
||||
|
||||
Returns (path_or_aumid, is_msix) tuple on Windows, path string on Linux.
|
||||
For MSIX installs, returns the AppUserModelId since the exe cannot be
|
||||
launched directly via subprocess from WindowsApps.
|
||||
"""
|
||||
if IS_WINDOWS:
|
||||
la = os.environ.get("LOCALAPPDATA", "")
|
||||
pf = os.environ.get("PROGRAMFILES", "")
|
||||
@@ -1741,8 +1747,8 @@ def detect_codex_desktop():
|
||||
]
|
||||
for p in desktop_paths:
|
||||
if p.exists():
|
||||
return str(p)
|
||||
# MSIX / Microsoft Store install: locate via Get-AppxPackage
|
||||
return str(p), False
|
||||
# MSIX / Microsoft Store install
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["powershell", "-NoProfile", "-Command",
|
||||
@@ -1753,13 +1759,70 @@ def detect_codex_desktop():
|
||||
if loc:
|
||||
msix_exe = Path(loc) / "app" / "Codex.exe"
|
||||
if msix_exe.exists():
|
||||
return str(msix_exe)
|
||||
r2 = subprocess.run(
|
||||
["powershell", "-NoProfile", "-Command",
|
||||
"(Get-AppxPackage *OpenAI.Codex*).PackageFamilyName"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
family = r2.stdout.strip() if r2.returncode == 0 else ""
|
||||
if family:
|
||||
return f"{family}!App", True
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
return None, False
|
||||
if START_SH and START_SH.exists():
|
||||
return str(START_SH)
|
||||
return None
|
||||
return str(START_SH), False
|
||||
return None, False
|
||||
|
||||
|
||||
def launch_codex_desktop(desktop_info):
|
||||
"""Launch Codex Desktop process.
|
||||
|
||||
Args:
|
||||
desktop_info: (path_or_aumid, is_msix) tuple from detect_codex_desktop()
|
||||
|
||||
Returns:
|
||||
subprocess.Popen object or None
|
||||
"""
|
||||
path, is_msix = desktop_info
|
||||
if IS_WINDOWS:
|
||||
if is_msix:
|
||||
return subprocess.Popen(
|
||||
["cmd", "/c", "start", "", f"shell:AppsFolder\\{path}"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||
return subprocess.Popen(
|
||||
[path],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||
else:
|
||||
return subprocess.Popen(
|
||||
[path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
preexec_fn=os.setsid)
|
||||
|
||||
|
||||
def is_codex_desktop_running():
|
||||
"""Check if Codex Desktop (or MSIX Codex) is currently running."""
|
||||
if IS_WINDOWS:
|
||||
try:
|
||||
for name in ("Codex Desktop.exe", "Codex.exe"):
|
||||
out = subprocess.run(
|
||||
["tasklist", "/FI", f"IMAGENAME eq {name}", "/FO", "CSV", "/NH"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
for line in out.stdout.strip().splitlines():
|
||||
parts = line.split(",")
|
||||
if len(parts) >= 2 and parts[1].strip('"').isdigit():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
out = subprocess.run(["pgrep", "-f", "/opt/codex-desktop/electron"], capture_output=True, text=True, timeout=5)
|
||||
return bool(out.stdout.strip())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def check_codex_auth():
|
||||
@@ -1802,24 +1865,25 @@ def last_log_lines(n=15):
|
||||
|
||||
def kill_existing_desktop(logfn=None):
|
||||
if IS_WINDOWS:
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["tasklist", "/FI", "IMAGENAME eq Codex Desktop.exe", "/FO", "CSV", "/NH"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
for line in out.stdout.strip().splitlines():
|
||||
parts = line.split(",")
|
||||
if len(parts) >= 2:
|
||||
pid_str = parts[1].strip('"')
|
||||
if pid_str.isdigit():
|
||||
pid = int(pid_str)
|
||||
_kill_process_group(pid)
|
||||
if logfn:
|
||||
logfn(f"Killed existing Codex Desktop (pid {pid})")
|
||||
time.sleep(2)
|
||||
except Exception as e:
|
||||
if logfn:
|
||||
logfn(f"Note: could not kill existing Desktop: {e}")
|
||||
for img in ("Codex Desktop.exe", "Codex.exe"):
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["tasklist", "/FI", f"IMAGENAME eq {img}", "/FO", "CSV", "/NH"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
for line in out.stdout.strip().splitlines():
|
||||
parts = line.split(",")
|
||||
if len(parts) >= 2:
|
||||
pid_str = parts[1].strip('"')
|
||||
if pid_str.isdigit():
|
||||
pid = int(pid_str)
|
||||
_kill_process_group(pid)
|
||||
if logfn:
|
||||
logfn(f"Killed existing Codex Desktop (pid {pid})")
|
||||
time.sleep(2)
|
||||
except Exception as e:
|
||||
if logfn:
|
||||
logfn(f"Note: could not kill existing Desktop: {e}")
|
||||
else:
|
||||
try:
|
||||
out = subprocess.run(["pgrep", "-f", "/opt/codex-desktop/electron"], capture_output=True, text=True, timeout=5)
|
||||
|
||||
Reference in New Issue
Block a user