Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 0 additions & 48 deletions codex-rs/core/src/context/world_state/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ use crate::context::environment_context::NetworkContext;
use crate::context::environment_context::push_xml_escaped_text;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::session::turn_context::TurnContext;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_protocol::protocol::TurnContextItem;
use codex_protocol::protocol::TurnContextNetworkItem;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
use serde::Serialize;
Expand Down Expand Up @@ -43,29 +39,6 @@ impl EnvironmentsState {
}
}

pub(crate) fn from_turn_context_item(turn_context_item: &TurnContextItem) -> Self {
Self {
environments: [(
LOCAL_ENVIRONMENT_ID.to_string(),
EnvironmentState {
cwd: PathUri::from_abs_path(&turn_context_item.cwd),
status: EnvironmentStatus::Available,
shell: None,
},
)]
.into_iter()
.collect(),
current_date: turn_context_item.current_date.clone(),
timezone: turn_context_item.timezone.clone(),
network: network_from_turn_context_item(turn_context_item),
filesystem: Some(FileSystemContext::from_permission_profile(
&turn_context_item.permission_profile(),
&workspace_roots_from_turn_context_item(turn_context_item),
)),
subagents: None,
}
}

pub(crate) fn with_subagents(mut self, subagents: String) -> Self {
if !subagents.is_empty() {
self.subagents = Some(subagents);
Expand Down Expand Up @@ -405,27 +378,6 @@ fn network_from_turn_context(turn_context: &TurnContext) -> Option<NetworkContex
))
}

fn network_from_turn_context_item(turn_context_item: &TurnContextItem) -> Option<NetworkContext> {
let TurnContextNetworkItem {
allowed_domains,
denied_domains,
} = turn_context_item.network.as_ref()?;
Some(NetworkContext::new(
allowed_domains.clone(),
denied_domains.clone(),
))
}

fn workspace_roots_from_turn_context_item(
turn_context_item: &TurnContextItem,
) -> Vec<AbsolutePathBuf> {
if let Some(workspace_roots) = turn_context_item.workspace_roots.as_ref() {
return workspace_roots.clone();
}

vec![turn_context_item.cwd.clone()]
}

#[cfg(test)]
#[path = "environment_tests.rs"]
mod tests;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::permissions::project_roots_glob_pattern;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::TurnContextItem;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::test_support::PathBufExt;
use core_test_support::test_path_buf;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -211,58 +209,6 @@ fn serialize_environment_context_with_full_filesystem_profile() {
assert_eq!(context.render(), expected);
}

#[test]
fn turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd() {
let repo = test_abs_path("/repo");
let other_repo = test_abs_path("/other-repo");
let repo_private = repo.join("private");
let item = TurnContextItem {
turn_id: None,
cwd: test_abs_path("/not-the-workspace"),
workspace_roots: Some(vec![repo.clone(), other_repo.clone()]),
current_date: None,
timezone: None,
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: Some(workspace_write_permission_profile_with_private_denials()),
network: None,
file_system_sandbox_policy: None,
model: "gpt-5".to_string(),
comp_hash: None,
personality: None,
collaboration_mode: None,
multi_agent_version: None,
multi_agent_mode: None,
realtime_active: None,
effort: None,
summary: codex_protocol::config_types::ReasoningSummary::Auto,
};

let context = EnvironmentsState::from_turn_context_item(&item).render();

assert!(
context.contains(&format!(
"<root>{}</root><root>{}</root>",
repo.to_string_lossy(),
other_repo.to_string_lossy()
)),
"{context}"
);
assert!(
context.contains(&format!("<path>{}</path>", repo_private.to_string_lossy())),
"{context}"
);
assert!(
!context.contains(
test_abs_path("/not-the-workspace")
.join("private")
.to_string_lossy()
.as_ref()
),
"{context}"
);
}

#[test]
fn serialize_read_only_environment_context() {
let context = environment_state(
Expand Down
26 changes: 26 additions & 0 deletions codex-rs/core/src/context/world_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ impl WorldStateSnapshot {
let current = Value::Object(self.sections.clone().into_iter().collect());
create_merge_patch(&previous, &current)
}

pub(crate) fn apply_merge_patch(&mut self, patch: &Value) -> serde_json::Result<()> {
let mut current = self.clone().into_value();
apply_merge_patch_value(&mut current, patch);
*self = serde_json::from_value(current)?;
Ok(())
}
}

impl fmt::Debug for WorldState {
Expand Down Expand Up @@ -193,6 +200,25 @@ fn create_merge_patch(previous: &Value, current: &Value) -> Option<Value> {
Some(Value::Object(patch))
}

fn apply_merge_patch_value(target: &mut Value, patch: &Value) {
let Value::Object(patch) = patch else {
target.clone_from(patch);
return;
};
if !target.is_object() {
*target = Value::Object(Map::new());
}
if let Value::Object(target) = target {
for (key, value) in patch {
if value.is_null() {
target.remove(key);
} else {
apply_merge_patch_value(target.entry(key.clone()).or_insert(Value::Null), value);
}
}
}
}

#[cfg(test)]
#[path = "world_state_tests.rs"]
mod tests;
10 changes: 9 additions & 1 deletion codex-rs/core/src/context/world_state/world_state_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ fn duplicate_section_ids_are_rejected() {

#[test]
fn snapshot_merge_patch_changes_and_removes_nested_values() {
let previous = WorldStateSnapshot {
let mut previous = WorldStateSnapshot {
sections: BTreeMap::from([
(
"kept".to_string(),
Expand All @@ -144,5 +144,13 @@ fn snapshot_merge_patch_changes_and_removes_nested_values() {
"removed_section": null,
}))
);
previous
.apply_merge_patch(
&current
.merge_patch_from(&previous)
.expect("changed snapshots should produce a patch"),
)
.expect("apply world-state merge patch");
assert_eq!(previous, current);
assert_eq!(current.merge_patch_from(&current), None);
}
30 changes: 26 additions & 4 deletions codex-rs/core/src/context_manager/history_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::*;
use crate::context::world_state::EnvironmentsState;
use crate::context::UserInstructions;
use crate::context::world_state::WorldState;
use crate::context::world_state::WorldStateSection;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_protocol::AgentPath;
Expand Down Expand Up @@ -75,13 +76,34 @@ fn create_history_with_items(items: Vec<ResponseItem>) -> ContextManager {
h
}

struct TestWorldStateSection;

impl WorldStateSection for TestWorldStateSection {
const ID: &'static str = "test";
type Snapshot = bool;

fn snapshot(&self) -> Self::Snapshot {
true
}

fn render_diff(
&self,
previous: Option<&Self::Snapshot>,
) -> Option<Box<dyn crate::context::ContextualUserFragment>> {
(previous != Some(&true)).then(|| {
Box::new(UserInstructions {
directory: None,
text: "test".to_string(),
}) as Box<dyn crate::context::ContextualUserFragment>
})
}
}

#[test]
fn world_state_baseline_deduplicates_until_history_is_replaced() {
let world_state = || {
let mut state = WorldState::default();
state.add_section(EnvironmentsState::from_turn_context_item(
&reference_context_item(),
));
state.add_section(TestWorldStateSection);
state
};
let mut history = ContextManager::new();
Expand Down
6 changes: 1 addition & 5 deletions codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ use self::turn::realtime_text_for_event;
use self::turn_context::TurnContext;
use self::turn_context::TurnSkillsContext;
use self::world_state::build_world_state_from_environment_snapshot;
use self::world_state::build_world_state_from_turn_context_item;
#[cfg(test)]
mod rollout_reconstruction_tests;

Expand Down Expand Up @@ -1385,6 +1384,7 @@ impl Session {
mut history,
previous_turn_settings,
reference_context_item,
world_state_baseline,
window_number,
first_window_id,
previous_window_id,
Expand All @@ -1397,10 +1397,6 @@ impl Session {
// will be processed again if the rollout is reconstructed in a future session.
// This meets image resizing requirements without modifying persisted rollouts.
prepare_response_items(&mut history);
let world_state_baseline = reference_context_item
.as_ref()
.map(build_world_state_from_turn_context_item)
.map(|world_state| world_state.snapshot());
{
let mut state = self.state.lock().await;
state.replace_history(history, reference_context_item);
Expand Down
Loading
Loading