From 90cc4e79a25f86e487a701732b7573139c683442 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Thu, 26 Feb 2026 15:17:35 -0800 Subject: [PATCH] feat: add local date/timezone to turn environment context (#12947) ## Summary This PR includes the session's local date and timezone in the model-visible environment context and persists that data in `TurnContextItem`. ## What changed - captures the current local date and IANA timezone when building a turn context, with a UTC fallback if the timezone lookup fails - includes current_date and timezone in the serialized payload - stores those fields on TurnContextItem so they survive rollout/history handling, subagent review threads, and resume flows - treats date/timezone changes as environment updates, so prompt caching and context refresh logic do not silently reuse stale time context - updates tests to validate the new environment fields without depending on a single hardcoded environment-context string ## test built a local build and saw it in the rollout file: ``` {"timestamp":"2026-02-26T21:39:50.737Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"\n zsh\n 2026-02-26\n America/Los_Angeles\n"}]}} ``` --- codex-rs/Cargo.lock | 1 + codex-rs/Cargo.toml | 1 + .../app-server/tests/suite/send_message.rs | 2 + codex-rs/core/Cargo.toml | 1 + codex-rs/core/src/codex.rs | 70 +++++++- codex-rs/core/src/environment_context.rs | 155 +++++++++++++++--- codex-rs/core/src/rollout/recorder.rs | 2 + codex-rs/core/tests/suite/prompt_caching.rs | 89 +++++++--- codex-rs/core/tests/suite/resume_warning.rs | 2 + codex-rs/protocol/src/protocol.rs | 6 + codex-rs/state/src/extract.rs | 4 + codex-rs/tui/src/lib.rs | 2 + 12 files changed, 288 insertions(+), 47 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 43c33c96a..a68f99425 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1791,6 +1791,7 @@ dependencies = [ "eventsource-stream", "futures", "http 1.4.0", + "iana-time-zone", "image", "indexmap 2.13.0", "insta", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 2784a1769..01836cde0 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -186,6 +186,7 @@ icu_locale_core = "2.1" icu_provider = { version = "2.1", features = ["sync"] } ignore = "0.4.23" image = { version = "^0.25.9", default-features = false } +iana-time-zone = "0.1.64" include_dir = "0.7.4" indexmap = "2.12.0" insta = "1.46.3" diff --git a/codex-rs/app-server/tests/suite/send_message.rs b/codex-rs/app-server/tests/suite/send_message.rs index e5c023d9d..e755a11b7 100644 --- a/codex-rs/app-server/tests/suite/send_message.rs +++ b/codex-rs/app-server/tests/suite/send_message.rs @@ -620,6 +620,8 @@ fn append_rollout_turn_context(path: &Path, timestamp: &str, model: &str) -> std item: RolloutItem::TurnContext(TurnContextItem { turn_id: None, cwd: PathBuf::from("/"), + current_date: None, + timezone: None, approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::DangerFullAccess, network: None, diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 47731291a..87f18eb80 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -61,6 +61,7 @@ env-flags = { workspace = true } eventsource-stream = { workspace = true } futures = { workspace = true } http = { workspace = true } +iana-time-zone = { workspace = true } indexmap = { workspace = true } keyring = { workspace = true, features = ["crypto-rust"] } libc = { workspace = true } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index bb071393d..6d583e46e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -53,6 +53,8 @@ use crate::util::error_or_panic; use crate::ws_version_from_features; use async_channel::Receiver; use async_channel::Sender; +use chrono::Local; +use chrono::Utc; use codex_hooks::HookEvent; use codex_hooks::HookEventAfterAgent; use codex_hooks::HookPayload; @@ -595,6 +597,8 @@ pub(crate) struct TurnContext { /// the model as well as sandbox policies are resolved against this path /// instead of `std::env::current_dir()`. pub(crate) cwd: PathBuf, + pub(crate) current_date: Option, + pub(crate) timezone: Option, pub(crate) developer_instructions: Option, pub(crate) compact_prompt: Option, pub(crate) user_instructions: Option, @@ -679,6 +683,8 @@ impl TurnContext { reasoning_summary: self.reasoning_summary, session_source: self.session_source.clone(), cwd: self.cwd.clone(), + current_date: self.current_date.clone(), + timezone: self.timezone.clone(), developer_instructions: self.developer_instructions.clone(), compact_prompt: self.compact_prompt.clone(), user_instructions: self.user_instructions.clone(), @@ -719,6 +725,8 @@ impl TurnContext { TurnContextItem { turn_id: Some(self.sub_id.clone()), cwd: self.cwd.clone(), + current_date: self.current_date.clone(), + timezone: self.timezone.clone(), approval_policy: self.approval_policy.value(), sandbox_policy: self.sandbox_policy.get().clone(), network: self.turn_context_network_item(), @@ -748,6 +756,16 @@ impl TurnContext { } } +fn local_time_context() -> (String, String) { + match iana_time_zone::get_timezone() { + Ok(timezone) => (Local::now().format("%Y-%m-%d").to_string(), timezone), + Err(_) => ( + Utc::now().format("%Y-%m-%d").to_string(), + "Etc/UTC".to_string(), + ), + } +} + #[derive(Clone)] pub(crate) struct SessionConfiguration { /// Provider identifier ("openai", "openrouter", ...). @@ -1017,6 +1035,7 @@ impl Session { .features .enabled(Feature::UseLinuxSandboxBwrap), )); + let (current_date, timezone) = local_time_context(); TurnContext { sub_id, config: per_turn_config.clone(), @@ -1028,6 +1047,8 @@ impl Session { reasoning_summary, session_source, cwd, + current_date: Some(current_date), + timezone: Some(timezone), developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration.user_instructions.clone(), @@ -4676,6 +4697,8 @@ async fn spawn_review_thread( tools_config, features: parent_turn_context.features.clone(), ghost_snapshot: parent_turn_context.ghost_snapshot.clone(), + current_date: parent_turn_context.current_date.clone(), + timezone: parent_turn_context.timezone.clone(), developer_instructions: None, user_instructions: None, compact_prompt: parent_turn_context.compact_prompt.clone(), @@ -7259,6 +7282,8 @@ mod tests { let previous_context_item = TurnContextItem { turn_id: Some(turn_context.sub_id.clone()), cwd: turn_context.cwd.clone(), + current_date: turn_context.current_date.clone(), + timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), sandbox_policy: turn_context.sandbox_policy.get().clone(), network: None, @@ -7296,6 +7321,8 @@ mod tests { let mut previous_context_item = TurnContextItem { turn_id: Some(turn_context.sub_id.clone()), cwd: turn_context.cwd.clone(), + current_date: turn_context.current_date.clone(), + timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), sandbox_policy: turn_context.sandbox_policy.get().clone(), network: None, @@ -7490,7 +7517,10 @@ mod tests { .record_context_updates_and_set_reference_context_item(&turn_context, None) .await; let history_after_second_seed = session.clone_history().await; - assert_eq!(expected, history_after_second_seed.raw_items()); + assert_eq!( + history_after_seed.raw_items(), + history_after_second_seed.raw_items() + ); } #[tokio::test] @@ -7658,6 +7688,8 @@ mod tests { let previous_context_item = TurnContextItem { turn_id: Some(turn_context.sub_id.clone()), cwd: turn_context.cwd.clone(), + current_date: turn_context.current_date.clone(), + timezone: turn_context.timezone.clone(), approval_policy: turn_context.approval_policy.value(), sandbox_policy: turn_context.sandbox_policy.get().clone(), network: None, @@ -8823,6 +8855,42 @@ mod tests { assert!(environment_update.contains("blocked.example.com")); } + #[tokio::test] + async fn build_settings_update_items_emits_environment_item_for_time_changes() { + let (session, previous_context) = make_session_and_context().await; + let previous_context = Arc::new(previous_context); + let mut current_context = previous_context + .with_model( + previous_context.model_info.slug.clone(), + &session.services.models_manager, + ) + .await; + current_context.current_date = Some("2026-02-27".to_string()); + current_context.timezone = Some("Europe/Berlin".to_string()); + + let reference_context_item = previous_context.to_turn_context_item(); + let update_items = session.build_settings_update_items( + Some(&reference_context_item), + None, + ¤t_context, + ); + + let environment_update = update_items + .iter() + .find_map(|item| match item { + ResponseItem::Message { role, content, .. } if role == "user" => { + let [ContentItem::InputText { text }] = content.as_slice() else { + return None; + }; + text.contains("").then_some(text) + } + _ => None, + }) + .expect("environment update item should be emitted"); + assert!(environment_update.contains("2026-02-27")); + assert!(environment_update.contains("Europe/Berlin")); + } + #[tokio::test] async fn record_context_updates_and_set_reference_context_item_injects_full_context_when_baseline_missing() { diff --git a/codex-rs/core/src/environment_context.rs b/codex-rs/core/src/environment_context.rs index c450818d6..3e9ed871e 100644 --- a/codex-rs/core/src/environment_context.rs +++ b/codex-rs/core/src/environment_context.rs @@ -13,6 +13,8 @@ use std::path::PathBuf; pub(crate) struct EnvironmentContext { pub cwd: Option, pub shell: Shell, + pub current_date: Option, + pub timezone: Option, pub network: Option, pub subagents: Option, } @@ -27,12 +29,16 @@ impl EnvironmentContext { pub fn new( cwd: Option, shell: Shell, + current_date: Option, + timezone: Option, network: Option, subagents: Option, ) -> Self { Self { cwd, shell, + current_date, + timezone, network, subagents, } @@ -44,11 +50,17 @@ impl EnvironmentContext { pub fn equals_except_shell(&self, other: &EnvironmentContext) -> bool { let EnvironmentContext { cwd, + current_date, + timezone, network, subagents, shell: _, } = other; - self.cwd == *cwd && self.network == *network && self.subagents == *subagents + self.cwd == *cwd + && self.current_date == *current_date + && self.timezone == *timezone + && self.network == *network + && self.subagents == *subagents } pub fn diff_from_turn_context_item( @@ -63,18 +75,22 @@ impl EnvironmentContext { } else { None }; + let current_date = after.current_date.clone(); + let timezone = after.timezone.clone(); let network = if before_network != after_network { after_network } else { before_network }; - EnvironmentContext::new(cwd, shell.clone(), network, None) + EnvironmentContext::new(cwd, shell.clone(), current_date, timezone, network, None) } pub fn from_turn_context(turn_context: &TurnContext, shell: &Shell) -> Self { Self::new( Some(turn_context.cwd.clone()), shell.clone(), + turn_context.current_date.clone(), + turn_context.timezone.clone(), Self::network_from_turn_context(turn_context), None, ) @@ -84,6 +100,8 @@ impl EnvironmentContext { Self::new( Some(turn_context_item.cwd.clone()), shell.clone(), + turn_context_item.current_date.clone(), + turn_context_item.timezone.clone(), Self::network_from_turn_context_item(turn_context_item), None, ) @@ -143,6 +161,12 @@ impl EnvironmentContext { let shell_name = self.shell.name(); lines.push(format!(" {shell_name}")); + if let Some(current_date) = self.current_date { + lines.push(format!(" {current_date}")); + } + if let Some(timezone) = self.timezone { + lines.push(format!(" {timezone}")); + } match self.network { Some(ref network) => { lines.push(" ".to_string()); @@ -193,12 +217,21 @@ mod tests { #[test] fn serialize_workspace_write_environment_context() { let cwd = test_path_buf("/repo"); - let context = EnvironmentContext::new(Some(cwd.clone()), fake_shell(), None, None); + let context = EnvironmentContext::new( + Some(cwd.clone()), + fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), + None, + None, + ); let expected = format!( r#" {cwd} bash + 2026-02-26 + America/Los_Angeles "#, cwd = cwd.display(), ); @@ -215,6 +248,8 @@ mod tests { let context = EnvironmentContext::new( Some(test_path_buf("/repo")), fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), Some(network), None, ); @@ -223,6 +258,8 @@ mod tests { r#" {} bash + 2026-02-26 + America/Los_Angeles api.example.com *.openai.com @@ -237,10 +274,19 @@ mod tests { #[test] fn serialize_read_only_environment_context() { - let context = EnvironmentContext::new(None, fake_shell(), None, None); + let context = EnvironmentContext::new( + None, + fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), + None, + None, + ); let expected = r#" bash + 2026-02-26 + America/Los_Angeles "#; assert_eq!(context.serialize_to_xml(), expected); @@ -248,10 +294,19 @@ mod tests { #[test] fn serialize_external_sandbox_environment_context() { - let context = EnvironmentContext::new(None, fake_shell(), None, None); + let context = EnvironmentContext::new( + None, + fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), + None, + None, + ); let expected = r#" bash + 2026-02-26 + America/Los_Angeles "#; assert_eq!(context.serialize_to_xml(), expected); @@ -259,10 +314,19 @@ mod tests { #[test] fn serialize_external_sandbox_with_restricted_network_environment_context() { - let context = EnvironmentContext::new(None, fake_shell(), None, None); + let context = EnvironmentContext::new( + None, + fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), + None, + None, + ); let expected = r#" bash + 2026-02-26 + America/Los_Angeles "#; assert_eq!(context.serialize_to_xml(), expected); @@ -270,10 +334,19 @@ mod tests { #[test] fn serialize_full_access_environment_context() { - let context = EnvironmentContext::new(None, fake_shell(), None, None); + let context = EnvironmentContext::new( + None, + fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), + None, + None, + ); let expected = r#" bash + 2026-02-26 + America/Los_Angeles "#; assert_eq!(context.serialize_to_xml(), expected); @@ -281,29 +354,65 @@ mod tests { #[test] fn equals_except_shell_compares_cwd() { - let context1 = - EnvironmentContext::new(Some(PathBuf::from("/repo")), fake_shell(), None, None); - let context2 = - EnvironmentContext::new(Some(PathBuf::from("/repo")), fake_shell(), None, None); + let context1 = EnvironmentContext::new( + Some(PathBuf::from("/repo")), + fake_shell(), + None, + None, + None, + None, + ); + let context2 = EnvironmentContext::new( + Some(PathBuf::from("/repo")), + fake_shell(), + None, + None, + None, + None, + ); assert!(context1.equals_except_shell(&context2)); } #[test] fn equals_except_shell_ignores_sandbox_policy() { - let context1 = - EnvironmentContext::new(Some(PathBuf::from("/repo")), fake_shell(), None, None); - let context2 = - EnvironmentContext::new(Some(PathBuf::from("/repo")), fake_shell(), None, None); + let context1 = EnvironmentContext::new( + Some(PathBuf::from("/repo")), + fake_shell(), + None, + None, + None, + None, + ); + let context2 = EnvironmentContext::new( + Some(PathBuf::from("/repo")), + fake_shell(), + None, + None, + None, + None, + ); assert!(context1.equals_except_shell(&context2)); } #[test] fn equals_except_shell_compares_cwd_differences() { - let context1 = - EnvironmentContext::new(Some(PathBuf::from("/repo1")), fake_shell(), None, None); - let context2 = - EnvironmentContext::new(Some(PathBuf::from("/repo2")), fake_shell(), None, None); + let context1 = EnvironmentContext::new( + Some(PathBuf::from("/repo1")), + fake_shell(), + None, + None, + None, + None, + ); + let context2 = EnvironmentContext::new( + Some(PathBuf::from("/repo2")), + fake_shell(), + None, + None, + None, + None, + ); assert!(!context1.equals_except_shell(&context2)); } @@ -319,6 +428,8 @@ mod tests { }, None, None, + None, + None, ); let context2 = EnvironmentContext::new( Some(PathBuf::from("/repo")), @@ -329,6 +440,8 @@ mod tests { }, None, None, + None, + None, ); assert!(context1.equals_except_shell(&context2)); @@ -339,6 +452,8 @@ mod tests { let context = EnvironmentContext::new( Some(test_path_buf("/repo")), fake_shell(), + Some("2026-02-26".to_string()), + Some("America/Los_Angeles".to_string()), None, Some("- agent-1: atlas\n- agent-2".to_string()), ); @@ -347,6 +462,8 @@ mod tests { r#" {} bash + 2026-02-26 + America/Los_Angeles - agent-1: atlas - agent-2 diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 0e68b8b25..bc2421927 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -1386,6 +1386,8 @@ mod tests { item: RolloutItem::TurnContext(TurnContextItem { turn_id: Some("turn-1".to_string()), cwd: latest_cwd.clone(), + current_date: None, + timezone: None, approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::new_read_only_policy(), network: None, diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index ff4adcb09..54ee11fba 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -44,14 +44,32 @@ fn text_user_input_parts(texts: Vec) -> serde_json::Value { }) } -fn default_env_context_str(cwd: &str, shell: &Shell) -> String { +fn assert_default_env_context(text: &str, cwd: &str, shell: &Shell) { let shell_name = shell.name(); - format!( - r#" - {cwd} - {shell_name} -"# - ) + assert!( + text.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG), + "expected environment context fragment: {text}" + ); + assert!( + text.contains(&format!("{cwd}")), + "expected cwd in environment context: {text}" + ); + assert!( + text.contains(&format!("{shell_name}")), + "expected shell in environment context: {text}" + ); + assert!( + text.contains("") && text.contains(""), + "expected current_date in environment context: {text}" + ); + assert!( + text.contains("") && text.contains(""), + "expected timezone in environment context: {text}" + ); + assert!( + text.ends_with(""), + "expected closing environment_context tag: {text}" + ); } fn assert_tool_names(body: &serde_json::Value, expected_names: &[&str]) { @@ -318,10 +336,13 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests let shell = default_user_shell(); let cwd_str = config.cwd.to_string_lossy(); - let expected_env_text = default_env_context_str(&cwd_str, &shell); + let env_text = input1[1]["content"][1]["text"] + .as_str() + .expect("environment context text"); + assert_default_env_context(env_text, &cwd_str, &shell); assert_eq!( - input1[1]["content"][1]["text"].as_str(), - Some(expected_env_text.as_str()), + input1[1]["content"][1]["type"].as_str(), + Some("input_text"), "expected environment context bundled after UI message in cached contextual message" ); assert_eq!(input1[2], text_user_input("hello 1".to_string())); @@ -523,6 +544,18 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul !env_texts.is_empty(), "expected environment context to be emitted: {env_texts:?}" ); + assert!( + env_texts + .iter() + .any(|text| text.contains("") && text.contains("")), + "expected current_date in environment context: {env_texts:?}" + ); + assert!( + env_texts + .iter() + .any(|text| text.contains("") && text.contains("")), + "expected timezone in environment context: {env_texts:?}" + ); let env_count = input .iter() @@ -672,21 +705,6 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res "role": "user", "content": [ { "type": "input_text", "text": "hello 2" } ] }); - let shell = default_user_shell(); - - let expected_env_text_2 = format!( - r#" - {} - {} -"#, - new_cwd.path().display(), - shell.name() - ); - let expected_env_msg_2 = serde_json::json!({ - "type": "message", - "role": "user", - "content": [ { "type": "input_text", "text": expected_env_text_2 } ] - }); let expected_permissions_msg = body1["input"][0].clone(); let body1_input = body1["input"].as_array().expect("input array"); let expected_settings_update_msg = body2["input"][body1_input.len()].clone(); @@ -704,6 +722,14 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res }), "expected model switch section after model override: {expected_settings_update_msg:?}" ); + let expected_env_msg_2 = body2["input"][body1_input.len() + 1].clone(); + assert_eq!(expected_env_msg_2["role"].as_str(), Some("user")); + let env_text = expected_env_msg_2["content"][0]["text"] + .as_str() + .expect("environment context text"); + let shell = default_user_shell(); + let expected_cwd = new_cwd.path().display().to_string(); + assert_default_env_context(env_text, &expected_cwd, &shell); let mut expected_body2 = body1_input.to_vec(); expected_body2.push(expected_settings_update_msg); expected_body2.push(expected_env_msg_2); @@ -798,13 +824,18 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a let shell = default_user_shell(); let default_cwd_lossy = default_cwd.to_string_lossy(); + let expected_env_text_1 = expected_ui_msg["content"][1]["text"] + .as_str() + .expect("cached environment context text") + .to_string(); + assert_default_env_context(&expected_env_text_1, &default_cwd_lossy, &shell); let expected_contextual_user_msg_1 = text_user_input_parts(vec![ expected_ui_msg["content"][0]["text"] .as_str() .expect("cached user instructions text") .to_string(), - default_env_context_str(&default_cwd_lossy, &shell), + expected_env_text_1, ]); let expected_user_message_1 = text_user_input("hello 1".to_string()); @@ -911,7 +942,11 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu let expected_ui_msg = body1["input"][1].clone(); let shell = default_user_shell(); - let expected_env_text_1 = default_env_context_str(&default_cwd.to_string_lossy(), &shell); + let expected_env_text_1 = expected_ui_msg["content"][1]["text"] + .as_str() + .expect("cached environment context text") + .to_string(); + assert_default_env_context(&expected_env_text_1, &default_cwd.to_string_lossy(), &shell); let expected_contextual_user_msg_1 = text_user_input_parts(vec![ expected_ui_msg["content"][0]["text"] .as_str() diff --git a/codex-rs/core/tests/suite/resume_warning.rs b/codex-rs/core/tests/suite/resume_warning.rs index a5f22e9f8..4742b417a 100644 --- a/codex-rs/core/tests/suite/resume_warning.rs +++ b/codex-rs/core/tests/suite/resume_warning.rs @@ -28,6 +28,8 @@ fn resume_history( let turn_ctx = TurnContextItem { turn_id: Some(turn_id.clone()), cwd: config.cwd.clone(), + current_date: None, + timezone: None, approval_policy: config.permissions.approval_policy.value(), sandbox_policy: config.permissions.sandbox_policy.get().clone(), network: None, diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 7a0c2fb5e..b5aaf47ea 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2133,6 +2133,10 @@ pub struct TurnContextItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub turn_id: Option, pub cwd: PathBuf, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub current_date: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timezone: Option, pub approval_policy: AskForApproval, pub sandbox_policy: SandboxPolicy, #[serde(skip_serializing_if = "Option::is_none")] @@ -3361,6 +3365,8 @@ mod tests { let item = TurnContextItem { turn_id: None, cwd: PathBuf::from("/tmp"), + current_date: None, + timezone: None, approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::DangerFullAccess, network: Some(TurnContextNetworkItem { diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 5d3afc86a..f97823897 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -252,6 +252,8 @@ mod tests { &RolloutItem::TurnContext(TurnContextItem { turn_id: Some("turn-1".to_string()), cwd: PathBuf::from("/parent/workspace"), + current_date: None, + timezone: None, approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::DangerFullAccess, network: None, @@ -286,6 +288,8 @@ mod tests { &RolloutItem::TurnContext(TurnContextItem { turn_id: Some("turn-1".to_string()), cwd: PathBuf::from("/fallback/workspace"), + current_date: None, + timezone: None, approval_policy: AskForApproval::OnRequest, sandbox_policy: SandboxPolicy::new_read_only_policy(), network: None, diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 5a4c87aef..8dfdbcd87 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -1272,6 +1272,8 @@ mod tests { TurnContextItem { turn_id: None, cwd, + current_date: None, + timezone: None, approval_policy: config.permissions.approval_policy.value(), sandbox_policy: config.permissions.sandbox_policy.get().clone(), network: None,