From b1421e6e1e820686a5a42e80ef2d1fe1154d2622 Mon Sep 17 00:00:00 2001
From: "Matt.Li"
Date: Sun, 13 Sep 2026 17:07:21 +0800
Subject: [PATCH] fix(codex): activate Windows packages through their
registered application
Resolve the exact registered manifest application for all Desktop launch paths and preserve CDP arguments. Keep standalone launches unchanged and report package lookup failures before closing processes.
Represent MSIX timezone support as renderer-only, including after CCSM restarts, without prompting an ineffective refresh.
Validation: 7 Windows source-harness tests, 3 UI tests, TypeScript, rustfmt, Prettier and diff checks. User cold-start reproduction confirmed direct launch error 5 versus successful package activation and CDP. Full Tauri build remains for CI.
---
src-tauri/src/codex_desktop.rs | 45 +++++-
src-tauri/src/codex_egress_timezone.rs | 67 ++++++--
src-tauri/src/codex_egress_timezone_tests.rs | 52 ++++++
src-tauri/src/codex_runtime_refresh.rs | 88 +++-------
src-tauri/src/codex_windows_launch.rs | 151 ++++++++++++++++++
src-tauri/src/settings.rs | 3 +-
.../CodexEgressTimezoneStatusCard.test.tsx | 14 ++
.../codex/CodexEgressTimezoneStatusCard.tsx | 8 +
src/lib/api/codexEgressTimezone.ts | 1 +
9 files changed, 343 insertions(+), 86 deletions(-)
create mode 100644 src-tauri/src/codex_windows_launch.rs
diff --git a/src-tauri/src/codex_desktop.rs b/src-tauri/src/codex_desktop.rs
index 8487235d..67e29904 100644
--- a/src-tauri/src/codex_desktop.rs
+++ b/src-tauri/src/codex_desktop.rs
@@ -10,6 +10,10 @@ use sha2::{Digest, Sha256};
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;
+#[cfg(target_os = "windows")]
+#[path = "codex_windows_launch.rs"]
+pub(crate) mod windows_launch;
+
pub(crate) const DEFAULT_CODEX_DEBUG_PORT: u16 = 9229;
pub(crate) const CDP_HTTP_TIMEOUT: Duration = Duration::from_secs(2);
const CDP_CONNECT_TIMEOUT: Duration = Duration::from_secs(4);
@@ -1776,6 +1780,10 @@ pub(crate) fn launch_codex_with_debug_port(
);
}
}
+ #[cfg(target_os = "windows")]
+ if let Some(app_id) = windows_launch::resolve_app_id(executable)? {
+ return launch_windows_app(&app_id, debug_port);
+ }
let mut command = Command::new(executable);
append_codex_debug_args(&mut command, debug_port);
apply_codex_launch_timezone(&mut command, launch_timezone.as_deref());
@@ -1802,13 +1810,25 @@ fn apply_codex_launch_timezone(command: &mut Command, timezone: Option<&str>) {
}
}
+#[cfg(target_os = "windows")]
+pub(crate) fn launch_windows_app(app_id: &str, debug_port: u16) -> Result<(), String> {
+ windows_launch::activate(app_id, debug_port)?;
+ // The activation broker does not inherit our per-child environment. Renderer
+ // timezone emulation is applied separately by install_script over CDP.
+ crate::codex_egress_timezone::mark_codex_timezone_not_inherited();
+ Ok(())
+}
+
+fn codex_debug_args(debug_port: u16) -> [String; 2] {
+ [
+ format!("--remote-debugging-port={debug_port}"),
+ format!("--remote-allow-origins=http://127.0.0.1:{debug_port}"),
+ ]
+}
+
/// 为 Desktop 启动命令追加 Chromium remote-debugging 参数。
fn append_codex_debug_args(command: &mut Command, debug_port: u16) {
- command
- .arg(format!("--remote-debugging-port={debug_port}"))
- .arg(format!(
- "--remote-allow-origins=http://127.0.0.1:{debug_port}"
- ));
+ command.args(codex_debug_args(debug_port));
}
/// Windows 下查找 Codex App 主进程的脚本。
@@ -2665,6 +2685,21 @@ fn version_tuple_from_package_name(name: &str) -> Vec {
mod tests {
use super::*;
+ #[test]
+ fn desktop_launch_transports_share_the_requested_debug_port() {
+ let mut command = Command::new("codex-desktop-placeholder");
+ append_codex_debug_args(&mut command, 9231);
+ let arguments: Vec<_> = command
+ .get_args()
+ .map(|arg| arg.to_string_lossy())
+ .collect();
+ assert_eq!(arguments, codex_debug_args(9231));
+ assert_eq!(
+ codex_debug_args(9231).join(" "),
+ "--remote-debugging-port=9231 --remote-allow-origins=http://127.0.0.1:9231"
+ );
+ }
+
/// 返回当前测试平台的 Desktop 主程序文件名。
fn desktop_test_executable_name() -> &'static str {
if cfg!(target_os = "windows") {
diff --git a/src-tauri/src/codex_egress_timezone.rs b/src-tauri/src/codex_egress_timezone.rs
index c40d912e..2352fd43 100644
--- a/src-tauri/src/codex_egress_timezone.rs
+++ b/src-tauri/src/codex_egress_timezone.rs
@@ -57,6 +57,7 @@ pub enum CodexEgressMonitorState {
Checking,
Ready,
RestartRequired,
+ RendererOnly,
Error,
}
@@ -67,6 +68,7 @@ pub(crate) struct CodexEgressMonitorRuntime {
pub last_trigger: Option,
pub last_error: Option,
pub restart_required: bool,
+ pub process_timezone_unavailable: bool,
pub consecutive_failures: u32,
}
@@ -98,11 +100,16 @@ pub(crate) fn monitor_status_from_parts(
runtime: &CodexEgressMonitorRuntime,
_now: i64,
) -> CodexEgressMonitorStatus {
- let state = if settings.mode != CodexEgressTimezoneMode::Auto {
+ let process_timezone_unavailable =
+ runtime.process_timezone_unavailable && settings.mode != CodexEgressTimezoneMode::Off;
+ let restart_required = runtime.restart_required && !process_timezone_unavailable;
+ let state = if process_timezone_unavailable {
+ CodexEgressMonitorState::RendererOnly
+ } else if settings.mode != CodexEgressTimezoneMode::Auto {
CodexEgressMonitorState::Disabled
} else if runtime.running {
CodexEgressMonitorState::Checking
- } else if runtime.restart_required {
+ } else if restart_required {
CodexEgressMonitorState::RestartRequired
} else if runtime.last_error.is_some() {
CodexEgressMonitorState::Error
@@ -133,7 +140,7 @@ pub(crate) fn monitor_status_from_parts(
.map(|at| at.saturating_add(i64::from(interval) * 60))
},
monitor_interval_minutes: interval,
- restart_required: runtime.restart_required,
+ restart_required,
}
}
@@ -816,6 +823,22 @@ pub(crate) fn notify_proxy_failure(app_type: &str, error: &ProxyError) {
pub(crate) fn mark_codex_timezone_applied() {
let applied_timezone = resolve_launch_timezone(&crate::settings::get_settings());
+ record_codex_launch_timezone(applied_timezone, false);
+}
+
+#[cfg(target_os = "windows")]
+pub(crate) fn mark_codex_timezone_not_inherited() {
+ let configured = resolve_launch_timezone(&crate::settings::get_settings());
+ if configured.is_some() {
+ log::warn!("Codex MSIX activation cannot inherit TZ; process timezone remains unapplied. Renderer timezone emulation will be attempted over CDP.");
+ }
+ record_codex_launch_timezone(None, true);
+}
+
+fn record_codex_launch_timezone(
+ applied_timezone: Option,
+ process_timezone_unavailable: bool,
+) {
if let Err(error) = crate::settings::mutate_codex_egress_timezone(|settings| {
settings.last_applied_timezone = applied_timezone;
settings.last_applied_at = Some(Utc::now().timestamp());
@@ -826,6 +849,7 @@ pub(crate) fn mark_codex_timezone_applied() {
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
runtime.restart_required = false;
+ runtime.process_timezone_unavailable = process_timezone_unavailable;
drop(runtime);
emit_monitor_status(None);
}
@@ -836,16 +860,21 @@ pub(crate) fn start_automatic_monitor(app_handle: AppHandle) {
return;
}
let settings = crate::settings::get_settings().codex_egress_timezone;
- if settings.mode == CodexEgressTimezoneMode::Auto
- && settings.detected_timezone.is_some()
- && settings.detected_timezone != settings.last_applied_timezone
- && crate::codex_desktop::is_codex_desktop_running()
- {
- monitor_runtime()
+ let running = crate::codex_desktop::detect_running_codex_main_process();
+ #[cfg(target_os = "windows")]
+ let packaged = running
+ .as_deref()
+ .is_some_and(crate::codex_desktop::windows_launch::is_packaged_codex);
+ #[cfg(not(target_os = "windows"))]
+ let packaged = false;
+ initialize_monitor_launch_state(
+ &mut monitor_runtime()
.lock()
- .unwrap_or_else(|poisoned| poisoned.into_inner())
- .restart_required = true;
- }
+ .unwrap_or_else(|poisoned| poisoned.into_inner()),
+ &settings,
+ running.is_some(),
+ packaged,
+ );
tauri::async_runtime::spawn(async move {
let mut last_tick = Instant::now();
loop {
@@ -864,6 +893,20 @@ pub(crate) fn start_automatic_monitor(app_handle: AppHandle) {
});
}
+pub(crate) fn initialize_monitor_launch_state(
+ runtime: &mut CodexEgressMonitorRuntime,
+ settings: &CodexEgressTimezoneSettings,
+ codex_running: bool,
+ packaged: bool,
+) {
+ runtime.process_timezone_unavailable = codex_running && packaged;
+ runtime.restart_required = !runtime.process_timezone_unavailable
+ && codex_running
+ && settings.mode == CodexEgressTimezoneMode::Auto
+ && settings.detected_timezone.is_some()
+ && settings.detected_timezone != settings.last_applied_timezone;
+}
+
#[tauri::command]
pub fn get_codex_egress_timezone_monitor_status() -> CodexEgressMonitorStatus {
current_monitor_status()
diff --git a/src-tauri/src/codex_egress_timezone_tests.rs b/src-tauri/src/codex_egress_timezone_tests.rs
index fbb56517..2104c329 100644
--- a/src-tauri/src/codex_egress_timezone_tests.rs
+++ b/src-tauri/src/codex_egress_timezone_tests.rs
@@ -368,6 +368,58 @@ fn monitor_status_exposes_failure_backoff_as_the_next_check() {
assert_eq!(status.state, CodexEgressMonitorState::Error);
assert_eq!(status.next_check_at, Some(2_180));
}
+
+#[test]
+fn package_activation_reports_renderer_only_without_a_refresh_loop() {
+ let runtime = CodexEgressMonitorRuntime {
+ process_timezone_unavailable: true,
+ restart_required: true,
+ ..CodexEgressMonitorRuntime::default()
+ };
+ for mode in [
+ CodexEgressTimezoneMode::Auto,
+ CodexEgressTimezoneMode::Manual,
+ ] {
+ let settings = CodexEgressTimezoneSettings {
+ mode,
+ ..CodexEgressTimezoneSettings::default()
+ };
+ let status = monitor_status_from_parts(&settings, &runtime, 2_010);
+ assert_eq!(status.state, CodexEgressMonitorState::RendererOnly);
+ assert!(!status.restart_required);
+ }
+ let status =
+ monitor_status_from_parts(&CodexEgressTimezoneSettings::default(), &runtime, 2_010);
+ assert_eq!(status.state, CodexEgressMonitorState::Disabled);
+}
+
+#[test]
+fn restarting_ccsm_preserves_the_running_package_timezone_limitation() {
+ let settings = CodexEgressTimezoneSettings {
+ mode: CodexEgressTimezoneMode::Auto,
+ detected_timezone: Some("Asia/Taipei".into()),
+ ..CodexEgressTimezoneSettings::default()
+ };
+ let mut runtime = CodexEgressMonitorRuntime::default();
+ super::codex_egress_timezone::initialize_monitor_launch_state(
+ &mut runtime,
+ &settings,
+ true,
+ true,
+ );
+ assert_eq!(
+ monitor_status_from_parts(&settings, &runtime, 0).state,
+ CodexEgressMonitorState::RendererOnly
+ );
+ assert!(!runtime.restart_required);
+ super::codex_egress_timezone::initialize_monitor_launch_state(
+ &mut runtime,
+ &settings,
+ true,
+ false,
+ );
+ assert!(runtime.restart_required);
+}
#[tokio::test]
#[ignore = "Explicit network diagnostic; never run as a regular test gate"]
async fn live_trace_transport_diagnostic() {
diff --git a/src-tauri/src/codex_runtime_refresh.rs b/src-tauri/src/codex_runtime_refresh.rs
index 83a4f40c..16343a36 100644
--- a/src-tauri/src/codex_runtime_refresh.rs
+++ b/src-tauri/src/codex_runtime_refresh.rs
@@ -851,31 +851,10 @@ async fn query_refresh_targets() -> Result {
Ok(classify_refresh_targets(&processes))
}
-#[cfg(target_os = "windows")]
-fn resolve_windows_codex_aumid() -> Option {
- let output = powershell_utf8_output(
- r#"
-[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
-Get-StartApps |
- Where-Object { $_.AppID -match '^OpenAI\.Codex(?:\.Preview)?_.*!App$' } |
- Select-Object -First 1 -ExpandProperty AppID
-"#,
- )
- .ok()?;
- let aumid = output.lines().next()?.trim();
- (!aumid.is_empty()).then(|| aumid.to_string())
-}
-
fn select_launch_target(
aumid: Option,
executable: Option,
- timezone_injection_enabled: bool,
) -> Option {
- if timezone_injection_enabled {
- if let Some(executable) = executable.clone() {
- return Some(CodexRuntimeLaunchTarget::DesktopExecutable(executable));
- }
- }
#[cfg(target_os = "windows")]
if let Some(aumid) = aumid {
return Some(CodexRuntimeLaunchTarget::WindowsAumid(aumid));
@@ -885,16 +864,16 @@ fn select_launch_target(
executable.map(CodexRuntimeLaunchTarget::DesktopExecutable)
}
-fn resolve_launch_target() -> Option {
+fn resolve_launch_target() -> Result
)}
+ {status?.state === "renderer_only" && (
+
+ Windows 应用包启动无法传入进程时区,只会尝试同步页面时区;
+ app-server 时区未覆盖,重复刷新无法解除此限制。
+
+ )}
{(requestError || status?.lastError) && (