define/emit some metrics for windows sandbox setup (#9573)

This should give us visibility into how users are using the elevated
sandbox nux flow, and the timing of the elevated setup.
This commit is contained in:
iceweasel-oai
2026-01-21 11:07:26 -08:00
committed by GitHub
Unverified
parent 8179312ff5
commit f81dd128a2
11 changed files with 273 additions and 24 deletions
+2
View File
@@ -1756,6 +1756,7 @@ dependencies = [
"codex-feedback",
"codex-file-search",
"codex-login",
"codex-otel",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
@@ -1831,6 +1832,7 @@ dependencies = [
"codex-feedback",
"codex-file-search",
"codex-login",
"codex-otel",
"codex-protocol",
"codex-tui",
"codex-utils-absolute-path",
+1
View File
@@ -39,6 +39,7 @@ codex-core = { workspace = true }
codex-feedback = { workspace = true }
codex-file-search = { workspace = true }
codex-login = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
color-eyre = { workspace = true }
+100 -12
View File
@@ -29,6 +29,7 @@ use crate::tui::TuiEvent;
use crate::update_action::UpdateAction;
use codex_ansi_escape::ansi_escape_line;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::config::edit::ConfigEdit;
@@ -47,6 +48,7 @@ use codex_core::protocol::Op;
use codex_core::protocol::SessionSource;
use codex_core::protocol::SkillErrorInfo;
use codex_core::protocol::TokenUsage;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelUpgrade;
@@ -70,6 +72,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use tokio::select;
use tokio::sync::broadcast;
use tokio::sync::mpsc::unbounded_channel;
@@ -326,6 +329,7 @@ async fn handle_model_migration_prompt_if_needed(
pub(crate) struct App {
pub(crate) server: Arc<ThreadManager>,
pub(crate) otel_manager: OtelManager,
pub(crate) app_event_tx: AppEventSender,
pub(crate) chat_widget: ChatWidget,
pub(crate) auth_manager: Arc<AuthManager>,
@@ -362,8 +366,7 @@ pub(crate) struct App {
/// stopping a thread (e.g., before starting a new one).
suppress_shutdown_complete: bool,
// One-shot suppression of the next world-writable scan after user confirmation.
skip_world_writable_scan_once: bool,
windows_sandbox: WindowsSandboxState,
// TODO(jif) drop once new UX is here.
// Track external agent approvals spawned via AgentControl.
@@ -373,6 +376,13 @@ pub(crate) struct App {
paused_codex_events: VecDeque<Event>,
}
#[derive(Default)]
struct WindowsSandboxState {
setup_started_at: Option<Instant>,
// One-shot suppression of the next world-writable scan after user confirmation.
skip_world_writable_scan_once: bool,
}
impl App {
pub fn chatwidget_init_for_forked_or_resumed_thread(
&self,
@@ -391,6 +401,7 @@ impl App {
feedback: self.feedback.clone(),
is_first_run: false,
model: Some(self.chat_widget.current_model().to_string()),
otel_manager: self.otel_manager.clone(),
}
}
@@ -450,6 +461,24 @@ impl App {
model = updated_model;
}
let auth = auth_manager.auth().await;
let auth_ref = auth.as_ref();
let model_info = thread_manager
.get_models_manager()
.get_model_info(model.as_str(), &config)
.await;
let otel_manager = OtelManager::new(
ThreadId::new(),
model.as_str(),
model_info.slug.as_str(),
auth_ref.and_then(CodexAuth::get_account_id),
auth_ref.and_then(CodexAuth::get_account_email),
auth_ref.map(|auth| auth.mode),
config.otel.log_user_prompt,
codex_core::terminal::user_agent(),
SessionSource::Cli,
);
let enhanced_keys_supported = tui.enhanced_keys_supported();
let mut chat_widget = match session_selection {
SessionSelection::StartFresh | SessionSelection::Exit => {
@@ -469,6 +498,7 @@ impl App {
feedback: feedback.clone(),
is_first_run,
model: Some(model.clone()),
otel_manager: otel_manager.clone(),
};
ChatWidget::new(init, thread_manager.clone())
}
@@ -496,6 +526,7 @@ impl App {
feedback: feedback.clone(),
is_first_run,
model: config.model.clone(),
otel_manager: otel_manager.clone(),
};
ChatWidget::new_from_existing(init, resumed.thread, resumed.session_configured)
}
@@ -523,6 +554,7 @@ impl App {
feedback: feedback.clone(),
is_first_run,
model: config.model.clone(),
otel_manager: otel_manager.clone(),
};
ChatWidget::new_from_existing(init, forked.thread, forked.session_configured)
}
@@ -536,6 +568,7 @@ impl App {
let mut app = Self {
server: thread_manager.clone(),
otel_manager: otel_manager.clone(),
app_event_tx,
chat_widget,
auth_manager: auth_manager.clone(),
@@ -553,7 +586,7 @@ impl App {
feedback: feedback.clone(),
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
windows_sandbox: WindowsSandboxState::default(),
external_approval_routes: HashMap::new(),
paused_codex_events: VecDeque::new(),
};
@@ -723,6 +756,7 @@ impl App {
feedback: self.feedback.clone(),
is_first_run: false,
model: Some(model),
otel_manager: self.otel_manager.clone(),
};
self.chat_widget = ChatWidget::new(init, self.server.clone());
if let Some(summary) = summary {
@@ -1060,7 +1094,16 @@ impl App {
self.chat_widget.open_windows_sandbox_enable_prompt(preset);
}
AppEvent::OpenWindowsSandboxFallbackPrompt { preset, reason } => {
self.otel_manager
.counter("codex.windows_sandbox.fallback_prompt_shown", 1, &[]);
self.chat_widget.clear_windows_sandbox_setup_status();
if let Some(started_at) = self.windows_sandbox.setup_started_at.take() {
self.otel_manager.record_duration(
"codex.windows_sandbox.elevated_setup_duration_ms",
started_at.elapsed(),
&[("result", "failure")],
);
}
self.chat_widget
.open_windows_sandbox_fallback_prompt(preset, reason);
}
@@ -1087,6 +1130,8 @@ impl App {
}
self.chat_widget.show_windows_sandbox_setup_status();
self.windows_sandbox.setup_started_at = Some(Instant::now());
let otel_manager = self.otel_manager.clone();
tokio::task::spawn_blocking(move || {
let result = codex_core::windows_sandbox::run_elevated_setup(
&policy,
@@ -1096,11 +1141,23 @@ impl App {
codex_home.as_path(),
);
let event = match result {
Ok(()) => AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset.clone(),
mode: WindowsSandboxEnableMode::Elevated,
},
Ok(()) => {
otel_manager.counter(
"codex.windows_sandbox.elevated_setup_success",
1,
&[],
);
AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset.clone(),
mode: WindowsSandboxEnableMode::Elevated,
}
}
Err(err) => {
otel_manager.counter(
"codex.windows_sandbox.elevated_setup_failure",
1,
&[],
);
tracing::error!(
error = %err,
"failed to run elevated Windows sandbox setup"
@@ -1123,6 +1180,13 @@ impl App {
#[cfg(target_os = "windows")]
{
self.chat_widget.clear_windows_sandbox_setup_status();
if let Some(started_at) = self.windows_sandbox.setup_started_at.take() {
self.otel_manager.record_duration(
"codex.windows_sandbox.elevated_setup_duration_ms",
started_at.elapsed(),
&[("result", "success")],
);
}
let profile = self.active_profile.as_deref();
let feature_key = Feature::WindowsSandbox.key();
let elevated_key = Feature::WindowsSandboxElevated.key();
@@ -1272,8 +1336,8 @@ impl App {
#[cfg(target_os = "windows")]
{
// One-shot suppression if the user just confirmed continue.
if self.skip_world_writable_scan_once {
self.skip_world_writable_scan_once = false;
if self.windows_sandbox.skip_world_writable_scan_once {
self.windows_sandbox.skip_world_writable_scan_once = false;
return Ok(AppRunControl::Continue);
}
@@ -1334,7 +1398,7 @@ impl App {
}
}
AppEvent::SkipNextWorldWritableScan => {
self.skip_world_writable_scan_once = true;
self.windows_sandbox.skip_world_writable_scan_once = true;
}
AppEvent::UpdateFullAccessWarningAcknowledged(ack) => {
self.chat_widget.set_full_access_warning_acknowledged(ack);
@@ -1766,11 +1830,14 @@ mod tests {
use codex_core::CodexAuth;
use codex_core::ThreadManager;
use codex_core::config::ConfigBuilder;
use codex_core::models_manager::manager::ModelsManager;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol::SessionConfiguredEvent;
use codex_core::protocol::SessionSource;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
@@ -1790,9 +1857,12 @@ mod tests {
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
let model = ModelsManager::get_model_offline(config.model.as_deref());
let otel_manager = test_otel_manager(&config, model.as_str());
App {
server,
otel_manager,
app_event_tx,
chat_widget,
auth_manager,
@@ -1810,7 +1880,7 @@ mod tests {
feedback: codex_feedback::CodexFeedback::new(),
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
windows_sandbox: WindowsSandboxState::default(),
external_approval_routes: HashMap::new(),
paused_codex_events: VecDeque::new(),
}
@@ -1830,10 +1900,13 @@ mod tests {
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
let model = ModelsManager::get_model_offline(config.model.as_deref());
let otel_manager = test_otel_manager(&config, model.as_str());
(
App {
server,
otel_manager,
app_event_tx,
chat_widget,
auth_manager,
@@ -1851,7 +1924,7 @@ mod tests {
feedback: codex_feedback::CodexFeedback::new(),
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
windows_sandbox: WindowsSandboxState::default(),
external_approval_routes: HashMap::new(),
paused_codex_events: VecDeque::new(),
},
@@ -1860,6 +1933,21 @@ mod tests {
)
}
fn test_otel_manager(config: &Config, model: &str) -> OtelManager {
let model_info = ModelsManager::construct_model_info_offline(model, config);
OtelManager::new(
ThreadId::new(),
model,
model_info.slug.as_str(),
None,
None,
None,
false,
"test".to_string(),
SessionSource::Cli,
)
}
fn all_model_presets() -> Vec<ModelPreset> {
codex_core::models_manager::model_presets::all_model_presets().clone()
}
+13
View File
@@ -89,6 +89,7 @@ use codex_core::protocol::WebSearchBeginEvent;
use codex_core::protocol::WebSearchEndEvent;
use codex_core::skills::model::SkillInterface;
use codex_core::skills::model::SkillMetadata;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::approvals::ElicitationRequestEvent;
@@ -359,6 +360,7 @@ pub(crate) struct ChatWidgetInit {
pub(crate) feedback: codex_feedback::CodexFeedback,
pub(crate) is_first_run: bool,
pub(crate) model: Option<String>,
pub(crate) otel_manager: OtelManager,
}
#[derive(Default)]
@@ -413,6 +415,7 @@ pub(crate) struct ChatWidget {
stored_collaboration_mode: CollaborationMode,
auth_manager: Arc<AuthManager>,
models_manager: Arc<ModelsManager>,
otel_manager: OtelManager,
session_header: SessionHeader,
initial_user_message: Option<UserMessage>,
token_info: Option<TokenUsageInfo>,
@@ -1816,6 +1819,7 @@ impl ChatWidget {
feedback,
is_first_run,
model,
otel_manager,
} = common;
let model = model.filter(|m| !m.trim().is_empty());
let mut config = config;
@@ -1867,6 +1871,7 @@ impl ChatWidget {
stored_collaboration_mode,
auth_manager,
models_manager,
otel_manager,
session_header: SessionHeader::new(model_for_header),
initial_user_message,
token_info: None,
@@ -1934,6 +1939,7 @@ impl ChatWidget {
models_manager,
feedback,
model,
otel_manager,
..
} = common;
let model = model.filter(|m| !m.trim().is_empty());
@@ -1981,6 +1987,7 @@ impl ChatWidget {
stored_collaboration_mode,
auth_manager,
models_manager,
otel_manager,
session_header: SessionHeader::new(header_model),
initial_user_message,
token_info: None,
@@ -2296,11 +2303,17 @@ impl ChatWidget {
return;
}
self.otel_manager.counter(
"codex.windows_sandbox.setup_elevated_sandbox_command",
1,
&[],
);
self.app_event_tx
.send(AppEvent::BeginWindowsSandboxElevatedSetup { preset });
}
#[cfg(not(target_os = "windows"))]
{
let _ = &self.otel_manager;
// Not supported; on non-Windows this command should never be reachable.
};
}
@@ -1,6 +1,6 @@
---
source: tui/src/chatwidget/tests.rs
assertion_line: 1980
assertion_line: 2654
expression: popup
---
Select Approval Mode
+21
View File
@@ -48,6 +48,7 @@ use codex_core::protocol::PatchApplyEndEvent;
use codex_core::protocol::RateLimitWindow;
use codex_core::protocol::ReviewRequest;
use codex_core::protocol::ReviewTarget;
use codex_core::protocol::SessionSource;
use codex_core::protocol::StreamErrorEvent;
use codex_core::protocol::TerminalInteractionEvent;
use codex_core::protocol::TokenCountEvent;
@@ -59,6 +60,7 @@ use codex_core::protocol::UndoCompletedEvent;
use codex_core::protocol::UndoStartedEvent;
use codex_core::protocol::ViewImageToolCallEvent;
use codex_core::protocol::WarningEvent;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::config_types::CollaborationMode;
@@ -700,6 +702,7 @@ async fn helpers_are_available_and_do_not_panic() {
let tx = AppEventSender::new(tx_raw);
let cfg = test_config().await;
let resolved_model = ModelsManager::get_model_offline(cfg.model.as_deref());
let otel_manager = test_otel_manager(&cfg, resolved_model.as_str());
let thread_manager = Arc::new(ThreadManager::with_models_provider(
CodexAuth::from_api_key("test"),
cfg.model_provider.clone(),
@@ -716,12 +719,28 @@ async fn helpers_are_available_and_do_not_panic() {
feedback: codex_feedback::CodexFeedback::new(),
is_first_run: true,
model: Some(resolved_model),
otel_manager,
};
let mut w = ChatWidget::new(init, thread_manager);
// Basic construction sanity.
let _ = &mut w;
}
fn test_otel_manager(config: &Config, model: &str) -> OtelManager {
let model_info = ModelsManager::construct_model_info_offline(model, config);
OtelManager::new(
ThreadId::new(),
model,
model_info.slug.as_str(),
None,
None,
None,
false,
"test".to_string(),
SessionSource::Cli,
)
}
// --- Helpers for tests that need direct construction and event draining ---
async fn make_chatwidget_manual(
model_override: Option<&str>,
@@ -740,6 +759,7 @@ async fn make_chatwidget_manual(
if let Some(model) = model_override {
cfg.model = Some(model.to_string());
}
let otel_manager = test_otel_manager(&cfg, resolved_model.as_str());
let mut bottom = BottomPane::new(BottomPaneParams {
app_event_tx: app_event_tx.clone(),
frame_requester: FrameRequester::test_dummy(),
@@ -782,6 +802,7 @@ async fn make_chatwidget_manual(
stored_collaboration_mode,
auth_manager,
models_manager,
otel_manager,
session_header: SessionHeader::new(resolved_model),
initial_user_message: None,
token_info: None,
+1
View File
@@ -40,6 +40,7 @@ codex-core = { workspace = true }
codex-feedback = { workspace = true }
codex-file-search = { workspace = true }
codex-login = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-tui = { workspace = true }
+98 -10
View File
@@ -45,6 +45,7 @@ use crate::tui::scrolling::TranscriptScroll;
use crate::update_action::UpdateAction;
use codex_ansi_escape::ansi_escape_line;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::config::edit::ConfigEdit;
@@ -63,6 +64,7 @@ use codex_core::protocol::SessionSource;
use codex_core::protocol::SkillErrorInfo;
use codex_core::protocol::TokenUsage;
use codex_core::terminal::terminal_info;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelUpgrade;
@@ -89,6 +91,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use tokio::select;
use tokio::sync::mpsc::unbounded_channel;
@@ -356,6 +359,7 @@ async fn handle_model_migration_prompt_if_needed(
pub(crate) struct App {
pub(crate) server: Arc<ThreadManager>,
pub(crate) otel_manager: OtelManager,
pub(crate) app_event_tx: AppEventSender,
pub(crate) chat_widget: ChatWidget,
pub(crate) auth_manager: Arc<AuthManager>,
@@ -418,6 +422,12 @@ pub(crate) struct App {
/// stopping a conversation (e.g., before starting a new one).
suppress_shutdown_complete: bool,
windows_sandbox: WindowsSandboxState,
}
#[derive(Default)]
struct WindowsSandboxState {
setup_started_at: Option<Instant>,
// One-shot suppression of the next world-writable scan after user confirmation.
skip_world_writable_scan_once: bool,
}
@@ -439,6 +449,7 @@ impl App {
feedback: self.feedback.clone(),
is_first_run: false,
model: Some(self.chat_widget.current_model().to_string()),
otel_manager: self.otel_manager.clone(),
}
}
@@ -498,6 +509,24 @@ impl App {
model = updated_model;
}
let auth = auth_manager.auth().await;
let auth_ref = auth.as_ref();
let model_info = thread_manager
.get_models_manager()
.get_model_info(model.as_str(), &config)
.await;
let otel_manager = OtelManager::new(
ThreadId::new(),
model.as_str(),
model_info.slug.as_str(),
auth_ref.and_then(CodexAuth::get_account_id),
auth_ref.and_then(CodexAuth::get_account_email),
auth_ref.map(|auth| auth.mode),
config.otel.log_user_prompt,
codex_core::terminal::user_agent(),
SessionSource::Cli,
);
let enhanced_keys_supported = tui.enhanced_keys_supported();
let mut chat_widget = match session_selection {
SessionSelection::StartFresh | SessionSelection::Exit => {
@@ -517,6 +546,7 @@ impl App {
feedback: feedback.clone(),
is_first_run,
model: Some(model.clone()),
otel_manager: otel_manager.clone(),
};
ChatWidget::new(init, thread_manager.clone())
}
@@ -544,6 +574,7 @@ impl App {
feedback: feedback.clone(),
is_first_run,
model: config.model.clone(),
otel_manager: otel_manager.clone(),
};
ChatWidget::new_from_existing(init, resumed.thread, resumed.session_configured)
}
@@ -571,6 +602,7 @@ impl App {
feedback: feedback.clone(),
is_first_run,
model: config.model.clone(),
otel_manager: otel_manager.clone(),
};
ChatWidget::new_from_existing(init, forked.thread, forked.session_configured)
}
@@ -600,6 +632,7 @@ impl App {
let mut app = Self {
server: thread_manager.clone(),
otel_manager: otel_manager.clone(),
app_event_tx,
chat_widget,
auth_manager: auth_manager.clone(),
@@ -628,7 +661,7 @@ impl App {
feedback: feedback.clone(),
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
windows_sandbox: WindowsSandboxState::default(),
};
// On startup, if Agent mode (workspace-write) or ReadOnly is active, warn about world-writable dirs on Windows.
@@ -1495,6 +1528,7 @@ impl App {
feedback: self.feedback.clone(),
is_first_run: false,
model: Some(model),
otel_manager: self.otel_manager.clone(),
};
self.chat_widget = ChatWidget::new(init, self.server.clone());
if let Some(summary) = summary {
@@ -1755,7 +1789,16 @@ impl App {
self.chat_widget.open_windows_sandbox_enable_prompt(preset);
}
AppEvent::OpenWindowsSandboxFallbackPrompt { preset, reason } => {
self.otel_manager
.counter("codex.windows_sandbox.fallback_prompt_shown", 1, &[]);
self.chat_widget.clear_windows_sandbox_setup_status();
if let Some(started_at) = self.windows_sandbox.setup_started_at.take() {
self.otel_manager.record_duration(
"codex.windows_sandbox.elevated_setup_duration_ms",
started_at.elapsed(),
&[("result", "failure")],
);
}
self.chat_widget
.open_windows_sandbox_fallback_prompt(preset, reason);
}
@@ -1782,6 +1825,8 @@ impl App {
}
self.chat_widget.show_windows_sandbox_setup_status();
self.windows_sandbox.setup_started_at = Some(Instant::now());
let otel_manager = self.otel_manager.clone();
tokio::task::spawn_blocking(move || {
let result = codex_core::windows_sandbox::run_elevated_setup(
&policy,
@@ -1791,11 +1836,23 @@ impl App {
codex_home.as_path(),
);
let event = match result {
Ok(()) => AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset.clone(),
mode: WindowsSandboxEnableMode::Elevated,
},
Ok(()) => {
otel_manager.counter(
"codex.windows_sandbox.elevated_setup_success",
1,
&[],
);
AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset.clone(),
mode: WindowsSandboxEnableMode::Elevated,
}
}
Err(err) => {
otel_manager.counter(
"codex.windows_sandbox.elevated_setup_failure",
1,
&[],
);
tracing::error!(
error = %err,
"failed to run elevated Windows sandbox setup"
@@ -1818,6 +1875,13 @@ impl App {
#[cfg(target_os = "windows")]
{
self.chat_widget.clear_windows_sandbox_setup_status();
if let Some(started_at) = self.windows_sandbox.setup_started_at.take() {
self.otel_manager.record_duration(
"codex.windows_sandbox.elevated_setup_duration_ms",
started_at.elapsed(),
&[("result", "success")],
);
}
let profile = self.active_profile.as_deref();
let feature_key = Feature::WindowsSandbox.key();
let elevated_key = Feature::WindowsSandboxElevated.key();
@@ -1967,8 +2031,8 @@ impl App {
#[cfg(target_os = "windows")]
{
// One-shot suppression if the user just confirmed continue.
if self.skip_world_writable_scan_once {
self.skip_world_writable_scan_once = false;
if self.windows_sandbox.skip_world_writable_scan_once {
self.windows_sandbox.skip_world_writable_scan_once = false;
return Ok(AppRunControl::Continue);
}
@@ -2026,7 +2090,7 @@ impl App {
}
}
AppEvent::SkipNextWorldWritableScan => {
self.skip_world_writable_scan_once = true;
self.windows_sandbox.skip_world_writable_scan_once = true;
}
AppEvent::UpdateFullAccessWarningAcknowledged(ack) => {
self.chat_widget.set_full_access_warning_acknowledged(ack);
@@ -2383,11 +2447,14 @@ mod tests {
use codex_core::CodexAuth;
use codex_core::ThreadManager;
use codex_core::config::ConfigBuilder;
use codex_core::models_manager::manager::ModelsManager;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol::SessionConfiguredEvent;
use codex_core::protocol::SessionSource;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
@@ -2407,9 +2474,12 @@ mod tests {
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
let model = ModelsManager::get_model_offline(config.model.as_deref());
let otel_manager = test_otel_manager(&config, model.as_str());
App {
server,
otel_manager,
app_event_tx,
chat_widget,
auth_manager,
@@ -2440,7 +2510,7 @@ mod tests {
feedback: codex_feedback::CodexFeedback::new(),
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
windows_sandbox: WindowsSandboxState::default(),
}
}
@@ -2458,10 +2528,13 @@ mod tests {
let auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
let model = ModelsManager::get_model_offline(config.model.as_deref());
let otel_manager = test_otel_manager(&config, model.as_str());
(
App {
server,
otel_manager,
app_event_tx,
chat_widget,
auth_manager,
@@ -2492,13 +2565,28 @@ mod tests {
feedback: codex_feedback::CodexFeedback::new(),
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
windows_sandbox: WindowsSandboxState::default(),
},
rx,
op_rx,
)
}
fn test_otel_manager(config: &Config, model: &str) -> OtelManager {
let model_info = ModelsManager::construct_model_info_offline(model, config);
OtelManager::new(
ThreadId::new(),
model,
model_info.slug.as_str(),
None,
None,
None,
false,
"test".to_string(),
SessionSource::Cli,
)
}
fn all_model_presets() -> Vec<ModelPreset> {
codex_core::models_manager::model_presets::all_model_presets().clone()
}
+13
View File
@@ -88,6 +88,7 @@ use codex_core::protocol::WebSearchBeginEvent;
use codex_core::protocol::WebSearchEndEvent;
use codex_core::skills::model::SkillInterface;
use codex_core::skills::model::SkillMetadata;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::approvals::ElicitationRequestEvent;
@@ -314,6 +315,7 @@ pub(crate) struct ChatWidgetInit {
pub(crate) feedback: codex_feedback::CodexFeedback,
pub(crate) is_first_run: bool,
pub(crate) model: Option<String>,
pub(crate) otel_manager: OtelManager,
}
#[derive(Default)]
@@ -360,6 +362,7 @@ pub(crate) struct ChatWidget {
stored_collaboration_mode: CollaborationMode,
auth_manager: Arc<AuthManager>,
models_manager: Arc<ModelsManager>,
otel_manager: OtelManager,
session_header: SessionHeader,
initial_user_message: Option<UserMessage>,
token_info: Option<TokenUsageInfo>,
@@ -1604,6 +1607,7 @@ impl ChatWidget {
feedback,
is_first_run,
model,
otel_manager,
} = common;
let model = model.filter(|m| !m.trim().is_empty());
let mut config = config;
@@ -1654,6 +1658,7 @@ impl ChatWidget {
stored_collaboration_mode,
auth_manager,
models_manager,
otel_manager,
session_header: SessionHeader::new(model_for_header),
initial_user_message,
token_info: None,
@@ -1718,6 +1723,7 @@ impl ChatWidget {
models_manager,
feedback,
model,
otel_manager,
..
} = common;
let model = model.filter(|m| !m.trim().is_empty());
@@ -1766,6 +1772,7 @@ impl ChatWidget {
stored_collaboration_mode,
auth_manager,
models_manager,
otel_manager,
session_header: SessionHeader::new(header_model),
initial_user_message,
token_info: None,
@@ -2053,11 +2060,17 @@ impl ChatWidget {
return;
}
self.otel_manager.counter(
"codex.windows_sandbox.setup_elevated_sandbox_command",
1,
&[],
);
self.app_event_tx
.send(AppEvent::BeginWindowsSandboxElevatedSetup { preset });
}
#[cfg(not(target_os = "windows"))]
{
let _ = &self.otel_manager;
// Not supported; on non-Windows this command should never be reachable.
};
}
@@ -1,5 +1,6 @@
---
source: tui2/src/chatwidget/tests.rs
assertion_line: 2370
expression: popup
---
Select Approval Mode
@@ -11,4 +12,3 @@ expression: popup
using.
Press enter to confirm or esc to go back
+22
View File
@@ -46,6 +46,7 @@ use codex_core::protocol::PatchApplyEndEvent;
use codex_core::protocol::RateLimitWindow;
use codex_core::protocol::ReviewRequest;
use codex_core::protocol::ReviewTarget;
use codex_core::protocol::SessionSource;
use codex_core::protocol::StreamErrorEvent;
use codex_core::protocol::TokenCountEvent;
use codex_core::protocol::TokenUsage;
@@ -56,6 +57,7 @@ use codex_core::protocol::UndoCompletedEvent;
use codex_core::protocol::UndoStartedEvent;
use codex_core::protocol::ViewImageToolCallEvent;
use codex_core::protocol::WarningEvent;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::config_types::CollaborationMode;
@@ -689,6 +691,8 @@ async fn helpers_are_available_and_do_not_panic() {
let tx = AppEventSender::new(tx_raw);
let cfg = test_config().await;
let model = cfg.model.clone();
let resolved_model = ModelsManager::get_model_offline(cfg.model.as_deref());
let otel_manager = test_otel_manager(&cfg, resolved_model.as_str());
let thread_manager = Arc::new(ThreadManager::with_models_provider(
CodexAuth::from_api_key("test"),
cfg.model_provider.clone(),
@@ -705,12 +709,28 @@ async fn helpers_are_available_and_do_not_panic() {
feedback: codex_feedback::CodexFeedback::new(),
is_first_run: true,
model,
otel_manager,
};
let mut w = ChatWidget::new(init, thread_manager);
// Basic construction sanity.
let _ = &mut w;
}
fn test_otel_manager(config: &Config, model: &str) -> OtelManager {
let model_info = ModelsManager::construct_model_info_offline(model, config);
OtelManager::new(
ThreadId::new(),
model,
model_info.slug.as_str(),
None,
None,
None,
false,
"test".to_string(),
SessionSource::Cli,
)
}
// --- Helpers for tests that need direct construction and event draining ---
async fn make_chatwidget_manual(
model_override: Option<&str>,
@@ -729,6 +749,7 @@ async fn make_chatwidget_manual(
if let Some(model) = model_override {
cfg.model = Some(model.to_string());
}
let otel_manager = test_otel_manager(&cfg, resolved_model.as_str());
let mut bottom = BottomPane::new(BottomPaneParams {
app_event_tx: app_event_tx.clone(),
frame_requester: FrameRequester::test_dummy(),
@@ -771,6 +792,7 @@ async fn make_chatwidget_manual(
stored_collaboration_mode,
auth_manager,
models_manager,
otel_manager,
session_header: SessionHeader::new(resolved_model),
initial_user_message: None,
token_info: None,