From f7baff7425c6c2a72402610a69cf75a693c05703 Mon Sep 17 00:00:00 2001 From: Roman | RyzenAdvanced Date: Wed, 27 May 2026 14:48:44 +0400 Subject: [PATCH] fix: default provider policy, anti-stall self-kill, _schema NameError (cobra PR #17) --- src/codex-launcher-gui.py | 65 +++++++++++----------- src/codex_launcher_lib.py | 112 ++++++++++++++++++++++++++++++-------- translate-proxy.py | 32 +++++++++-- 3 files changed, 150 insertions(+), 59 deletions(-) diff --git a/src/codex-launcher-gui.py b/src/codex-launcher-gui.py index ed4ee8e..81d87a5 100644 --- a/src/codex-launcher-gui.py +++ b/src/codex-launcher-gui.py @@ -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})") diff --git a/src/codex_launcher_lib.py b/src/codex_launcher_lib.py index b494e81..a78747a 100644 --- a/src/codex_launcher_lib.py +++ b/src/codex_launcher_lib.py @@ -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) diff --git a/translate-proxy.py b/translate-proxy.py index ad20e3d..e84b907 100755 --- a/translate-proxy.py +++ b/translate-proxy.py @@ -2017,12 +2017,17 @@ _PROVIDER_POLICIES = { "tool_output_limit": 8000, "max_input_items": 250}, } +_DEFAULT_PROVIDER_POLICY = { + "compaction": "balanced", "context_size": 128000, + "tool_output_limit": 6000, "max_input_items": 60, +} + def provider_policy(target_url=None, backend=None): host = urllib.parse.urlparse(target_url or TARGET_URL).netloc.lower() for key, policy in _PROVIDER_POLICIES.items(): if key in host: return policy - return {} + return dict(_DEFAULT_PROVIDER_POLICY) # ═══════════════════════════════════════════════════════════════════ # Adaptive context compaction (model-aware) @@ -7247,7 +7252,8 @@ class Handler(http.server.BaseHTTPRequestHandler): print(f"[{self._session_id}] [smart-continue] XML injection retry failed: {e}", file=sys.stderr) break _nudge_msg = {"role": "user", "content": nudge_text} - nudge_messages = oa_input_to_messages(_preprocess_vision_input(input_data, _schema) if _schema and not _schema.supports_vision else input_data) + [_nudge_msg] + _nudge_schema = _load_schema(model=model) + nudge_messages = oa_input_to_messages(_preprocess_vision_input(input_data, _nudge_schema) if _nudge_schema and not _nudge_schema.supports_vision else input_data) + [_nudge_msg] instructions = body.get("instructions", "").strip() if instructions: nudge_messages.insert(0, {"role": "system", "content": instructions}) @@ -8180,7 +8186,8 @@ def _handle_shutdown_signal(sig, frame): def _anti_stall_cleanup(): my_pid = os.getpid() - my_port = PORT + my_ppid = os.getppid() + my_pgid = os.getpgid(0) killed = [] try: import subprocess as _sp @@ -8190,7 +8197,24 @@ def _anti_stall_cleanup(): if not pid_str or not pid_str.isdigit(): continue pid = int(pid_str) - if pid == my_pid: + if pid == my_pid or pid == my_ppid: + continue + try: + pgid = os.getpgid(pid) + if pgid == my_pgid: + continue + except OSError: + pass + try: + stat = open(f"/proc/{pid}/stat").read().split() + start_ticks = int(stat[21]) + import time as _t + ticks_per_sec = os.sysconf('SC_CLK_TCK') + start_time = start_ticks / ticks_per_sec + age = _t.time() - start_time + if age < 60: + continue + except Exception: continue try: os.kill(pid, signal.SIGTERM)