diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index 52a9e34972..a0f5de420c 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -104,9 +104,27 @@ async fn app_server_cyber_policy_error_renders_dedicated_notice() { let rendered = lines_to_single_string(&cells[0]); assert!(rendered.contains("This content can't be shown")); assert!(rendered.contains("extra caution with cybersecurity requests")); + assert!(rendered.contains("openai.com/form/enterprise-trusted-access-for-cyber")); assert!(!rendered.contains("server fallback message")); } +#[tokio::test] +async fn app_server_cyber_policy_error_uses_individual_link_for_personal_plan() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await; + chat.plan_type = Some(PlanType::Free); + chat.has_chatgpt_account = true; + + handle_error( + &mut chat, + "server fallback message", + Some(CodexErrorInfo::CyberPolicy), + ); + + let cells = drain_insert_history(&mut rx); + assert_eq!(cells.len(), 1); + assert!(lines_to_single_string(&cells[0]).contains("https://chatgpt.com/cyber/")); +} + #[tokio::test] async fn app_server_model_verification_renders_warning() { let (mut chat, mut rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await; diff --git a/codex-rs/tui/src/chatwidget/turn_runtime.rs b/codex-rs/tui/src/chatwidget/turn_runtime.rs index 305813a095..dcf29f8739 100644 --- a/codex-rs/tui/src/chatwidget/turn_runtime.rs +++ b/codex-rs/tui/src/chatwidget/turn_runtime.rs @@ -385,7 +385,12 @@ impl ChatWidget { pub(super) fn on_cyber_policy_error(&mut self) { self.input_queue.submit_pending_steers_after_interrupt = false; self.finalize_turn(); - self.add_to_history(history_cell::new_cyber_policy_error_event()); + let plan_type = if self.has_chatgpt_account { + self.plan_type + } else { + None + }; + self.add_to_history(history_cell::new_cyber_policy_error_event(plan_type)); self.request_redraw(); // After an error ends the turn, try sending the next queued input. diff --git a/codex-rs/tui/src/history_cell/notices.rs b/codex-rs/tui/src/history_cell/notices.rs index f1affa840b..f1453d8288 100644 --- a/codex-rs/tui/src/history_cell/notices.rs +++ b/codex-rs/tui/src/history_cell/notices.rs @@ -93,6 +93,9 @@ pub(crate) struct SafetyAccessBlockCell { const SAFETY_ACCESS_BLOCK_TITLE: &str = "This content can't be shown"; const SAFETY_ACCESS_BLOCK_LEARN_MORE_URL: &str = "https://help.openai.com/en/articles/20001326"; +const CYBER_INDIVIDUAL_TRUSTED_ACCESS_URL: &str = "https://chatgpt.com/cyber/"; +const CYBER_ENTERPRISE_TRUSTED_ACCESS_URL: &str = + "https://openai.com/form/enterprise-trusted-access-for-cyber/"; pub(crate) fn new_safety_access_block_event() -> SafetyAccessBlockCell { SafetyAccessBlockCell { @@ -101,10 +104,16 @@ pub(crate) fn new_safety_access_block_event() -> SafetyAccessBlockCell { } } -pub(crate) fn new_cyber_policy_error_event() -> SafetyAccessBlockCell { +pub(crate) fn new_cyber_policy_error_event(plan_type: Option) -> SafetyAccessBlockCell { + let trusted_access_url = match plan_type { + Some( + PlanType::Free | PlanType::Go | PlanType::Plus | PlanType::Pro | PlanType::ProLite, + ) => CYBER_INDIVIDUAL_TRUSTED_ACCESS_URL, + _ => CYBER_ENTERPRISE_TRUSTED_ACCESS_URL, + }; SafetyAccessBlockCell { body: "We take extra caution with cybersecurity requests. If you’re a security professional, you may be able to apply for Trusted Access.", - trusted_access_url: "https://openai.com/form/enterprise-trusted-access-for-cyber/", + trusted_access_url, } } diff --git a/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__cyber_policy_error_event_individual_snapshot.snap b/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__cyber_policy_error_event_individual_snapshot.snap new file mode 100644 index 0000000000..3d0fc82df3 --- /dev/null +++ b/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__cyber_policy_error_event_individual_snapshot.snap @@ -0,0 +1,9 @@ +--- +source: tui/src/history_cell/tests.rs +expression: rendered +--- +ⓘ This content can't be shown + We take extra caution with cybersecurity requests. If you’re a security + professional, you may be able to apply for Trusted Access. + Trusted Access: https://chatgpt.com/cyber/ + Learn more: https://help.openai.com/en/articles/20001326 diff --git a/codex-rs/tui/src/history_cell/tests.rs b/codex-rs/tui/src/history_cell/tests.rs index 2c847b955f..5333aadc31 100644 --- a/codex-rs/tui/src/history_cell/tests.rs +++ b/codex-rs/tui/src/history_cell/tests.rs @@ -752,7 +752,14 @@ fn ps_output_multiline_snapshot() { #[test] fn cyber_policy_error_event_snapshot() { - let cell = new_cyber_policy_error_event(); + let cell = new_cyber_policy_error_event(/*plan_type*/ None); + let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n"); + insta::assert_snapshot!(rendered); +} + +#[test] +fn cyber_policy_error_event_individual_snapshot() { + let cell = new_cyber_policy_error_event(Some(PlanType::Pro)); let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n"); insta::assert_snapshot!(rendered); } @@ -766,7 +773,7 @@ fn safety_access_block_event_snapshot() { #[test] fn cyber_policy_error_event_narrow_snapshot() { - let cell = new_cyber_policy_error_event(); + let cell = new_cyber_policy_error_event(/*plan_type*/ None); let rendered = render_lines(&cell.display_lines(/*width*/ 36)).join("\n"); insta::assert_snapshot!(rendered); }