From 94d228516219e76af1ffa7daf6e98d30345e1a56 Mon Sep 17 00:00:00 2001 From: daaimengermengzhu <217037449+daaimengermengzhu@users.noreply.github.com> Date: Tue, 26 May 2026 21:37:27 +0800 Subject: [PATCH] fix: add opt-in direct Windows Codex launch mode --- apps/codex-plus-launcher/src/main.rs | 3 +- apps/codex-plus-manager/src/App.tsx | 31 ++++++++++- crates/codex-plus-core/src/launcher.rs | 42 +++++++++++++-- crates/codex-plus-core/src/settings.rs | 51 +++++++++++++++++-- crates/codex-plus-core/tests/bridge_routes.rs | 3 +- crates/codex-plus-core/tests/launcher.rs | 50 +++++++++++++++++- 6 files changed, 168 insertions(+), 12 deletions(-) diff --git a/apps/codex-plus-launcher/src/main.rs b/apps/codex-plus-launcher/src/main.rs index 4244f3af..ad74b39e 100644 --- a/apps/codex-plus-launcher/src/main.rs +++ b/apps/codex-plus-launcher/src/main.rs @@ -172,9 +172,10 @@ impl LaunchHooks for LauncherHooks { app_dir: &Path, debug_port: u16, extra_args: &[String], + windows_codex_launch_mode: codex_plus_core::settings::WindowsCodexLaunchMode, ) -> anyhow::Result { self.core - .launch_codex(app_dir, debug_port, extra_args) + .launch_codex(app_dir, debug_port, extra_args, windows_codex_launch_mode) .await } diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index a00db01e..77c89906 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -93,6 +93,7 @@ type OverviewResult = CommandResult<{ type BackendSettings = { codexAppPath: string; codexExtraArgs: string[]; + windowsCodexLaunchMode: WindowsCodexLaunchMode; providerSyncEnabled: boolean; enhancementsEnabled: boolean; launchMode: LaunchMode; @@ -108,6 +109,7 @@ type BackendSettings = { }; type LaunchMode = "patch" | "relay"; +type WindowsCodexLaunchMode = "packagedActivation" | "directProcess"; type RelayProfile = { id: string; @@ -308,6 +310,7 @@ const routes: Array<{ id: Route; label: string; icon: LucideIcon }> = [ const defaultSettings: BackendSettings = { codexAppPath: "", codexExtraArgs: [], + windowsCodexLaunchMode: "packagedActivation", providerSyncEnabled: false, enhancementsEnabled: true, launchMode: "patch", @@ -1864,6 +1867,22 @@ function SettingsScreen({ />

每行一个参数,例如 --force_high_performance_gpu。不需要填写 open 或 --args。

+ +

+ 开启后,WindowsApps 版 Codex 会跳过系统应用激活,先关闭旧 Codex 进程,再直接启动官方 Codex.exe。 +

@@ -2664,7 +2683,13 @@ function normalizeSettings(settings: BackendSettings): BackendSettings { const activeRelayId = profiles.some((profile) => profile.id === settings.activeRelayId) ? settings.activeRelayId : profiles[0]?.id || "default"; - return syncLegacyRelayFields({ ...defaultSettings, ...settings, relayProfiles: profiles, activeRelayId }); + return syncLegacyRelayFields({ + ...defaultSettings, + ...settings, + windowsCodexLaunchMode: normalizeWindowsCodexLaunchMode(settings.windowsCodexLaunchMode), + relayProfiles: profiles, + activeRelayId, + }); } function codexExtraArgsToInput(args: string[] | undefined) { @@ -2675,6 +2700,10 @@ function inputToCodexExtraArgs(value: string) { return value === "" ? [] : value.split(/\r?\n/); } +function normalizeWindowsCodexLaunchMode(value: string | undefined): WindowsCodexLaunchMode { + return value === "directProcess" ? "directProcess" : "packagedActivation"; +} + function normalizeRelayProfile(profile: RelayProfile): RelayProfile { const legacyMixedApi = profile.relayMode === "mixedApi"; const normalized: RelayProfile = { diff --git a/crates/codex-plus-core/src/launcher.rs b/crates/codex-plus-core/src/launcher.rs index 57cd0e2a..92f394f2 100644 --- a/crates/codex-plus-core/src/launcher.rs +++ b/crates/codex-plus-core/src/launcher.rs @@ -13,7 +13,9 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::process::{Child, Command}; use tokio::sync::Mutex; -use crate::settings::{BackendSettings, SettingsStore, normalize_codex_extra_args}; +use crate::settings::{ + BackendSettings, SettingsStore, WindowsCodexLaunchMode, normalize_codex_extra_args, +}; use crate::status::{LaunchStatus, StatusStore}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -131,6 +133,7 @@ pub trait LaunchHooks: Send + Sync { app_dir: &Path, debug_port: u16, extra_args: &[String], + windows_codex_launch_mode: WindowsCodexLaunchMode, ) -> anyhow::Result; async fn bridge_context( &self, @@ -212,7 +215,12 @@ where } let launch = hooks - .launch_codex(&app_dir, debug_port, &settings.codex_extra_args) + .launch_codex( + &app_dir, + debug_port, + &settings.codex_extra_args, + settings.windows_codex_launch_mode, + ) .await?; launched = Some(launch.clone()); @@ -367,9 +375,21 @@ impl LaunchHooks for DefaultLaunchHooks { app_dir: &Path, debug_port: u16, extra_args: &[String], + windows_codex_launch_mode: WindowsCodexLaunchMode, ) -> anyhow::Result { if cfg!(windows) { - if let Some(activation) = build_packaged_activation(app_dir, debug_port, extra_args) { + if should_stop_existing_codex_before_direct_windows_launch( + app_dir, + windows_codex_launch_mode, + ) { + crate::watcher::stop_codex_processes(); + } + + if should_use_packaged_activation(app_dir, windows_codex_launch_mode) { + let Some(activation) = build_packaged_activation(app_dir, debug_port, extra_args) + else { + unreachable!("packaged activation precondition was checked"); + }; let CodexLaunch::PackagedActivation { app_user_model_id, arguments, @@ -992,6 +1012,22 @@ pub fn build_packaged_activation( }) } +pub fn should_use_packaged_activation( + app_dir: &Path, + windows_codex_launch_mode: WindowsCodexLaunchMode, +) -> bool { + windows_codex_launch_mode == WindowsCodexLaunchMode::PackagedActivation + && crate::app_paths::packaged_app_user_model_id(app_dir).is_some() +} + +pub fn should_stop_existing_codex_before_direct_windows_launch( + app_dir: &Path, + windows_codex_launch_mode: WindowsCodexLaunchMode, +) -> bool { + windows_codex_launch_mode == WindowsCodexLaunchMode::DirectProcess + && crate::app_paths::packaged_app_user_model_id(app_dir).is_some() +} + pub fn codex_process_environment() -> HashMap { let env = std::env::vars().collect::>(); codex_process_environment_from(&env, crate::proxy::detect_system_proxy) diff --git a/crates/codex-plus-core/src/settings.rs b/crates/codex-plus-core/src/settings.rs index 388f473d..37b98c2d 100644 --- a/crates/codex-plus-core/src/settings.rs +++ b/crates/codex-plus-core/src/settings.rs @@ -13,6 +13,14 @@ pub enum LaunchMode { Relay, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub enum WindowsCodexLaunchMode { + #[default] + PackagedActivation, + DirectProcess, +} + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct RelayProfile { @@ -76,6 +84,8 @@ pub struct BackendSettings { pub codex_app_path: String, #[serde(rename = "codexExtraArgs", default)] pub codex_extra_args: Vec, + #[serde(rename = "windowsCodexLaunchMode", default)] + pub windows_codex_launch_mode: WindowsCodexLaunchMode, #[serde(rename = "providerSyncEnabled", default)] pub provider_sync_enabled: bool, #[serde(rename = "enhancementsEnabled", default = "default_true")] @@ -111,6 +121,7 @@ impl Default for BackendSettings { Self { codex_app_path: String::new(), codex_extra_args: Vec::new(), + windows_codex_launch_mode: WindowsCodexLaunchMode::PackagedActivation, provider_sync_enabled: false, enhancements_enabled: true, launch_mode: LaunchMode::Patch, @@ -315,16 +326,24 @@ fn merge_known_setting_fields(target: &mut Map, source: &Map anyhow::Result { Ok(CodexLaunch::Process { command: vec!["codex".to_string()], diff --git a/crates/codex-plus-core/tests/launcher.rs b/crates/codex-plus-core/tests/launcher.rs index d4033153..8c4f6e83 100644 --- a/crates/codex-plus-core/tests/launcher.rs +++ b/crates/codex-plus-core/tests/launcher.rs @@ -11,13 +11,16 @@ use codex_plus_core::launcher::{ CodexLaunch, DefaultLaunchHooks, LaunchHooks, LaunchOptions, MacosCleanupPolicy, build_codex_arguments, build_codex_command, build_macos_cleanup_command, build_macos_open_command, build_packaged_activation, codex_process_environment_from, - launch_and_inject_with_hooks, with_temporary_proxy_environment, + launch_and_inject_with_hooks, should_stop_existing_codex_before_direct_windows_launch, + should_use_packaged_activation, with_temporary_proxy_environment, }; #[cfg(windows)] use codex_plus_core::launcher::{WindowsProcessControlStrategy, windows_process_control_strategy}; use codex_plus_core::ports::select_platform_loopback_port_with; use codex_plus_core::proxy::has_proxy_environment; -use codex_plus_core::settings::{BackendSettings, RelayProfile, RelayProtocol}; +use codex_plus_core::settings::{ + BackendSettings, RelayProfile, RelayProtocol, WindowsCodexLaunchMode, +}; use codex_plus_core::status::StatusStore; #[test] @@ -259,6 +262,48 @@ fn launcher_packaged_activation_can_preserve_process_id() { assert_eq!(launch.process_id(), Some(4242)); } +#[test] +fn launcher_uses_packaged_activation_by_default_for_windows_packages() { + let app_dir = PathBuf::from( + r"C:\Program Files\WindowsApps\OpenAI.Codex_26.506.2212.0_x64__2p2nqsd0c76g0\app", + ); + + assert!(should_use_packaged_activation( + &app_dir, + WindowsCodexLaunchMode::PackagedActivation + )); + assert!(!should_stop_existing_codex_before_direct_windows_launch( + &app_dir, + WindowsCodexLaunchMode::PackagedActivation + )); +} + +#[test] +fn launcher_direct_windows_mode_skips_packaged_activation_and_requires_clean_slate() { + let app_dir = PathBuf::from( + r"C:\Program Files\WindowsApps\OpenAI.Codex_26.506.2212.0_x64__2p2nqsd0c76g0\app", + ); + + assert!(!should_use_packaged_activation( + &app_dir, + WindowsCodexLaunchMode::DirectProcess + )); + assert!(should_stop_existing_codex_before_direct_windows_launch( + &app_dir, + WindowsCodexLaunchMode::DirectProcess + )); +} + +#[test] +fn launcher_direct_windows_mode_does_not_stop_portable_codex_instances() { + let app_dir = PathBuf::from(r"C:\Portable\Codex\app"); + + assert!(!should_stop_existing_codex_before_direct_windows_launch( + &app_dir, + WindowsCodexLaunchMode::DirectProcess + )); +} + #[cfg(windows)] #[test] fn launcher_windows_packaged_process_management_uses_native_api() { @@ -1012,6 +1057,7 @@ impl LaunchHooks for FakeHooks { app_dir: &Path, debug_port: u16, extra_args: &[String], + _windows_codex_launch_mode: WindowsCodexLaunchMode, ) -> anyhow::Result { assert!(app_dir.ends_with("Codex.app")); if extra_args.is_empty() {