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,
|
recover_config_if_needed, write_config_for_native, write_config_for_translated,
|
||||||
endpoint_models_url, endpoint_model_headers, fetch_models_for_endpoint,
|
endpoint_models_url, endpoint_model_headers, fetch_models_for_endpoint,
|
||||||
refresh_endpoint_models, run_endpoint_doctor,
|
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,
|
last_log_lines, kill_existing_desktop, safe_cleanup_owned,
|
||||||
start_proxy_for, stop_proxy, start_bgp_proxy, get_proxy_state, set_proxy_state,
|
start_proxy_for, stop_proxy, start_bgp_proxy, get_proxy_state, set_proxy_state,
|
||||||
detect_terminal, open_url, open_file, write_secure_text,
|
detect_terminal, open_url, open_file, write_secure_text,
|
||||||
@@ -2338,9 +2338,10 @@ class LauncherWin:
|
|||||||
|
|
||||||
desk_row = ttk.Frame(main)
|
desk_row = ttk.Frame(main)
|
||||||
desk_row.pack(fill="x", pady=(2, 0))
|
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="✓ 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:
|
else:
|
||||||
ttk.Label(desk_row, text="✗ Codex Desktop -- not found", foreground="#d29922").pack(side="left")
|
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))
|
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 = []
|
self._missing = []
|
||||||
if not self._cli_info:
|
if not self._cli_info:
|
||||||
self._missing.append("cli")
|
self._missing.append("cli")
|
||||||
if not self._desktop_info:
|
if not self._desktop_info[0]:
|
||||||
self._missing.append("desktop")
|
self._missing.append("desktop")
|
||||||
|
|
||||||
# Auth status
|
# Auth status
|
||||||
@@ -2461,8 +2462,9 @@ class LauncherWin:
|
|||||||
self.log(f"✓ Codex CLI detected ({ver})")
|
self.log(f"✓ Codex CLI detected ({ver})")
|
||||||
else:
|
else:
|
||||||
self.log("✗ Codex CLI NOT found -- CLI launch disabled.")
|
self.log("✗ Codex CLI NOT found -- CLI launch disabled.")
|
||||||
if self._desktop_info:
|
if self._desktop_info[0]:
|
||||||
self.log(f"✓ Codex Desktop detected ({self._desktop_info})")
|
label = "MSIX (Store)" if self._desktop_info[1] else self._desktop_info[0]
|
||||||
|
self.log(f"✓ Codex Desktop detected ({label})")
|
||||||
else:
|
else:
|
||||||
self.log("✗ Codex Desktop NOT found -- Desktop launch disabled.")
|
self.log("✗ Codex Desktop NOT found -- Desktop launch disabled.")
|
||||||
if self._missing:
|
if self._missing:
|
||||||
@@ -3132,7 +3134,7 @@ class LauncherWin:
|
|||||||
|
|
||||||
# ── Launch ───────────────────────────────────────────────────────
|
# ── Launch ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _set_busy(self, busy):
|
def _set_busy(self, busy, proxy_alive=False):
|
||||||
has_cli = "cli" not in self._missing
|
has_cli = "cli" not in self._missing
|
||||||
has_desk = "desktop" not in self._missing
|
has_desk = "desktop" not in self._missing
|
||||||
def _update():
|
def _update():
|
||||||
@@ -3140,8 +3142,8 @@ class LauncherWin:
|
|||||||
self._btn_cli.configure(state="disabled" if busy or not has_cli else "normal")
|
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_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._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._kill_btn.configure(state="normal" if busy or proxy_alive else "disabled")
|
||||||
self._restart_btn.configure(state="normal" if busy else "disabled")
|
self._restart_btn.configure(state="normal" if busy or proxy_alive else "disabled")
|
||||||
self._root.after(0, _update)
|
self._root.after(0, _update)
|
||||||
|
|
||||||
def _launch(self, target):
|
def _launch(self, target):
|
||||||
@@ -3228,7 +3230,7 @@ class LauncherWin:
|
|||||||
finally:
|
finally:
|
||||||
if keep_session_alive:
|
if keep_session_alive:
|
||||||
self.log("Warm-start handoff detected; keeping proxy/config active for running Desktop.")
|
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.")
|
self.log("Ready. Use Kill && Cleanup when finished.")
|
||||||
else:
|
else:
|
||||||
stop_proxy()
|
stop_proxy()
|
||||||
@@ -3292,25 +3294,31 @@ class LauncherWin:
|
|||||||
self.log("Ready.")
|
self.log("Ready.")
|
||||||
|
|
||||||
def _launch_desktop(self, ep, model):
|
def _launch_desktop(self, ep, model):
|
||||||
desktop_path = self._desktop_info
|
if not self._desktop_info[0]:
|
||||||
if not desktop_path:
|
|
||||||
self.log("ERROR: Codex Desktop not found")
|
self.log("ERROR: Codex Desktop not found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if IS_WINDOWS:
|
_, is_msix = self._desktop_info
|
||||||
self._proc = subprocess.Popen(
|
self._proc = launch_codex_desktop(self._desktop_info)
|
||||||
[desktop_path],
|
if not self._proc:
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
self.log("ERROR: Failed to launch Codex Desktop")
|
||||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
return False
|
||||||
else:
|
|
||||||
self._proc = subprocess.Popen(
|
|
||||||
[desktop_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
||||||
preexec_fn=os.setsid)
|
|
||||||
|
|
||||||
pid = self._proc.pid
|
pid = self._proc.pid
|
||||||
self.log(f"Desktop started (PID {pid})")
|
self.log(f"Desktop started (PID {pid})")
|
||||||
self.log(f"Log: {LAUNCH_LOG}")
|
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()
|
t0 = time.time()
|
||||||
stall_warned = False
|
stall_warned = False
|
||||||
while self._proc and self._proc.poll() is None:
|
while self._proc and self._proc.poll() is None:
|
||||||
@@ -3366,18 +3374,13 @@ class LauncherWin:
|
|||||||
|
|
||||||
def _launch_desktop_direct(self):
|
def _launch_desktop_direct(self):
|
||||||
self.log("Launching Codex Desktop (default OAuth)...")
|
self.log("Launching Codex Desktop (default OAuth)...")
|
||||||
desktop_path = self._desktop_info
|
if not self._desktop_info[0]:
|
||||||
if not desktop_path:
|
|
||||||
self.log("ERROR: Codex Desktop not found")
|
self.log("ERROR: Codex Desktop not found")
|
||||||
return
|
return
|
||||||
if IS_WINDOWS:
|
self._proc = launch_codex_desktop(self._desktop_info)
|
||||||
self._proc = subprocess.Popen(
|
if not self._proc:
|
||||||
[desktop_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
self.log("ERROR: Failed to launch Codex Desktop")
|
||||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
return
|
||||||
else:
|
|
||||||
self._proc = subprocess.Popen(
|
|
||||||
[desktop_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
||||||
preexec_fn=os.setsid)
|
|
||||||
pid = self._proc.pid
|
pid = self._proc.pid
|
||||||
self.log(f"Desktop started (PID {pid})")
|
self.log(f"Desktop started (PID {pid})")
|
||||||
|
|
||||||
|
|||||||
@@ -1729,6 +1729,12 @@ def detect_codex_cli():
|
|||||||
|
|
||||||
|
|
||||||
def detect_codex_desktop():
|
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:
|
if IS_WINDOWS:
|
||||||
la = os.environ.get("LOCALAPPDATA", "")
|
la = os.environ.get("LOCALAPPDATA", "")
|
||||||
pf = os.environ.get("PROGRAMFILES", "")
|
pf = os.environ.get("PROGRAMFILES", "")
|
||||||
@@ -1741,8 +1747,8 @@ def detect_codex_desktop():
|
|||||||
]
|
]
|
||||||
for p in desktop_paths:
|
for p in desktop_paths:
|
||||||
if p.exists():
|
if p.exists():
|
||||||
return str(p)
|
return str(p), False
|
||||||
# MSIX / Microsoft Store install: locate via Get-AppxPackage
|
# MSIX / Microsoft Store install
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
["powershell", "-NoProfile", "-Command",
|
["powershell", "-NoProfile", "-Command",
|
||||||
@@ -1753,13 +1759,70 @@ def detect_codex_desktop():
|
|||||||
if loc:
|
if loc:
|
||||||
msix_exe = Path(loc) / "app" / "Codex.exe"
|
msix_exe = Path(loc) / "app" / "Codex.exe"
|
||||||
if msix_exe.exists():
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None, False
|
||||||
if START_SH and START_SH.exists():
|
if START_SH and START_SH.exists():
|
||||||
return str(START_SH)
|
return str(START_SH), False
|
||||||
return None
|
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():
|
def check_codex_auth():
|
||||||
@@ -1802,24 +1865,25 @@ def last_log_lines(n=15):
|
|||||||
|
|
||||||
def kill_existing_desktop(logfn=None):
|
def kill_existing_desktop(logfn=None):
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
try:
|
for img in ("Codex Desktop.exe", "Codex.exe"):
|
||||||
out = subprocess.run(
|
try:
|
||||||
["tasklist", "/FI", "IMAGENAME eq Codex Desktop.exe", "/FO", "CSV", "/NH"],
|
out = subprocess.run(
|
||||||
capture_output=True, text=True, timeout=5,
|
["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(",")
|
for line in out.stdout.strip().splitlines():
|
||||||
if len(parts) >= 2:
|
parts = line.split(",")
|
||||||
pid_str = parts[1].strip('"')
|
if len(parts) >= 2:
|
||||||
if pid_str.isdigit():
|
pid_str = parts[1].strip('"')
|
||||||
pid = int(pid_str)
|
if pid_str.isdigit():
|
||||||
_kill_process_group(pid)
|
pid = int(pid_str)
|
||||||
if logfn:
|
_kill_process_group(pid)
|
||||||
logfn(f"Killed existing Codex Desktop (pid {pid})")
|
if logfn:
|
||||||
time.sleep(2)
|
logfn(f"Killed existing Codex Desktop (pid {pid})")
|
||||||
except Exception as e:
|
time.sleep(2)
|
||||||
if logfn:
|
except Exception as e:
|
||||||
logfn(f"Note: could not kill existing Desktop: {e}")
|
if logfn:
|
||||||
|
logfn(f"Note: could not kill existing Desktop: {e}")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
out = subprocess.run(["pgrep", "-f", "/opt/codex-desktop/electron"], capture_output=True, text=True, timeout=5)
|
out = subprocess.run(["pgrep", "-f", "/opt/codex-desktop/electron"], capture_output=True, text=True, timeout=5)
|
||||||
|
|||||||
@@ -2017,12 +2017,17 @@ _PROVIDER_POLICIES = {
|
|||||||
"tool_output_limit": 8000, "max_input_items": 250},
|
"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):
|
def provider_policy(target_url=None, backend=None):
|
||||||
host = urllib.parse.urlparse(target_url or TARGET_URL).netloc.lower()
|
host = urllib.parse.urlparse(target_url or TARGET_URL).netloc.lower()
|
||||||
for key, policy in _PROVIDER_POLICIES.items():
|
for key, policy in _PROVIDER_POLICIES.items():
|
||||||
if key in host:
|
if key in host:
|
||||||
return policy
|
return policy
|
||||||
return {}
|
return dict(_DEFAULT_PROVIDER_POLICY)
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
# Adaptive context compaction (model-aware)
|
# 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)
|
print(f"[{self._session_id}] [smart-continue] XML injection retry failed: {e}", file=sys.stderr)
|
||||||
break
|
break
|
||||||
_nudge_msg = {"role": "user", "content": nudge_text}
|
_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()
|
instructions = body.get("instructions", "").strip()
|
||||||
if instructions:
|
if instructions:
|
||||||
nudge_messages.insert(0, {"role": "system", "content": instructions})
|
nudge_messages.insert(0, {"role": "system", "content": instructions})
|
||||||
@@ -8180,7 +8186,8 @@ def _handle_shutdown_signal(sig, frame):
|
|||||||
|
|
||||||
def _anti_stall_cleanup():
|
def _anti_stall_cleanup():
|
||||||
my_pid = os.getpid()
|
my_pid = os.getpid()
|
||||||
my_port = PORT
|
my_ppid = os.getppid()
|
||||||
|
my_pgid = os.getpgid(0)
|
||||||
killed = []
|
killed = []
|
||||||
try:
|
try:
|
||||||
import subprocess as _sp
|
import subprocess as _sp
|
||||||
@@ -8190,7 +8197,24 @@ def _anti_stall_cleanup():
|
|||||||
if not pid_str or not pid_str.isdigit():
|
if not pid_str or not pid_str.isdigit():
|
||||||
continue
|
continue
|
||||||
pid = int(pid_str)
|
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
|
continue
|
||||||
try:
|
try:
|
||||||
os.kill(pid, signal.SIGTERM)
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
|||||||
Reference in New Issue
Block a user