From 8530440b9111dde8db91bb4a912df14ef81822d4 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Wed, 15 Jul 2026 19:48:46 +0000 Subject: [PATCH] Refactor world-state rendering tests into snapshots (#33412) ## What changed - Add a shared test helper that renders before-and-after section states alongside the resulting role and content. - Convert the agents, environment, apps, and plugins world-state tests to snapshot tables covering absent, unknown, unchanged, changed, and removed states. GitOrigin-RevId: 03372a5f925d2019f37bf5a5a2f060261899026c --- .../context/world_state/agents_md_tests.rs | 118 ++-------- .../world_state/apps_instructions_tests.rs | 45 ++-- .../context/world_state/environment_tests.rs | 207 ++++-------------- codex-rs/core/src/context/world_state/mod.rs | 2 + .../world_state/plugins_instructions_tests.rs | 48 ++-- ...ld_state__agents_md__tests__snapshots.snap | 51 +++++ ...__apps_instructions__tests__snapshots.snap | 39 ++++ ..._state__environment__tests__snapshots.snap | 78 +++++++ ...lugins_instructions__tests__snapshots.snap | 47 ++++ .../src/context/world_state/test_support.rs | 83 +++++++ 10 files changed, 395 insertions(+), 323 deletions(-) create mode 100644 codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap create mode 100644 codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap create mode 100644 codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap create mode 100644 codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap create mode 100644 codex-rs/core/src/context/world_state/test_support.rs diff --git a/codex-rs/core/src/context/world_state/agents_md_tests.rs b/codex-rs/core/src/context/world_state/agents_md_tests.rs index d39fe6c727..d5644ec42c 100644 --- a/codex-rs/core/src/context/world_state/agents_md_tests.rs +++ b/codex-rs/core/src/context/world_state/agents_md_tests.rs @@ -1,101 +1,29 @@ +use super::super::PreviousSectionState; +use super::super::test_support::render_section_cases; use super::*; -use crate::context::world_state::WorldState; -use codex_protocol::models::ContentItem; -use codex_protocol::models::ResponseItem; -use pretty_assertions::assert_eq; -use serde_json::json; #[test] -fn renders_full_state_and_omits_unchanged_state() { - let loaded = LoadedAgentsMd::from_text_for_testing("use the project formatter"); - let mut state = WorldState::default(); - state.add_section(AgentsMdState::new(Some(&loaded))); +fn snapshots() { + use PreviousSectionState::Absent; + use PreviousSectionState::Known; + use PreviousSectionState::Unknown; - assert_eq!( - vec![user_message( - "# AGENTS.md instructions\n\n\nuse the project formatter\n", - )], - render_fragments(state.render_full()), - ); - assert_eq!( - Vec::::new(), - render_fragments(state.render_diff(&state.snapshot())) - ); - assert_eq!( - state.snapshot().into_value(), - json!({"agents_md": {"text": "use the project formatter"}}), - ); -} + let empty = AgentsMdState::default(); + let project_formatter = LoadedAgentsMd::from_text_for_testing("use the project formatter"); + let project_formatter = AgentsMdState::new(Some(&project_formatter)); + let old = LoadedAgentsMd::from_text_for_testing("old instructions"); + let old = AgentsMdState::new(Some(&old)); + let new = LoadedAgentsMd::from_text_for_testing("new instructions"); + let new = AgentsMdState::new(Some(&new)); -#[test] -fn changed_and_removed_state_supersedes_previous_instructions() { - let previous_loaded = LoadedAgentsMd::from_text_for_testing("old instructions"); - let mut previous = WorldState::default(); - previous.add_section(AgentsMdState::new(Some(&previous_loaded))); - - let current_loaded = LoadedAgentsMd::from_text_for_testing("new instructions"); - let mut current = WorldState::default(); - current.add_section(AgentsMdState::new(Some(¤t_loaded))); - assert_eq!( - vec![user_message( - "# AGENTS.md instructions\n\n\nThese AGENTS.md instructions replace all previously provided AGENTS.md instructions.\n\nnew instructions\n", - )], - render_fragments(current.render_diff(&previous.snapshot())), - ); - - let mut removed = WorldState::default(); - removed.add_section(AgentsMdState::default()); - assert_eq!( - vec![user_message( - "# AGENTS.md instructions\n\n\nThe previously provided AGENTS.md instructions no longer apply.\n", - )], - render_fragments(removed.render_diff(¤t.snapshot())), - ); -} - -#[test] -fn unknown_previous_state_is_explicitly_superseded() { - let loaded = LoadedAgentsMd::from_text_for_testing("current instructions"); - let current = AgentsMdState::new(Some(&loaded)); - assert_eq!( - vec![user_message( - "# AGENTS.md instructions\n\n\nThese AGENTS.md instructions replace all previously provided AGENTS.md instructions.\n\ncurrent instructions\n", - )], - render_fragments(vec![ - WorldStateSection::render_diff(¤t, PreviousSectionState::Unknown) - .expect("unknown state should be replaced"), - ]), - ); - - assert_eq!( - vec![user_message( - "# AGENTS.md instructions\n\n\nThe previously provided AGENTS.md instructions no longer apply.\n", - )], - render_fragments(vec![ - WorldStateSection::render_diff( - &AgentsMdState::default(), - PreviousSectionState::Unknown, - ) - .expect("unknown state should be removed"), - ]), - ); -} - -fn render_fragments(fragments: Vec>) -> Vec { - fragments - .into_iter() - .map(ContextualUserFragment::into_boxed_response_item) - .collect() -} - -fn user_message(text: &str) -> ResponseItem { - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: text.to_string(), - }], - phase: None, - internal_chat_message_metadata_passthrough: None, - } + insta::assert_snapshot!(render_section_cases(&[ + (Absent, Absent), + (Absent, Known(&empty)), + (Absent, Known(&project_formatter)), + (Known(&project_formatter), Known(&project_formatter)), + (Known(&old), Known(&new)), + (Known(&new), Known(&empty)), + (Unknown, Known(&new)), + (Unknown, Known(&empty)), + ])); } diff --git a/codex-rs/core/src/context/world_state/apps_instructions_tests.rs b/codex-rs/core/src/context/world_state/apps_instructions_tests.rs index 92f6613594..dc67acf048 100644 --- a/codex-rs/core/src/context/world_state/apps_instructions_tests.rs +++ b/codex-rs/core/src/context/world_state/apps_instructions_tests.rs @@ -1,40 +1,29 @@ use super::*; use crate::context::ContextualUserFragment; +use crate::context::world_state::PreviousSectionState; +use crate::context::world_state::test_support::render_section_cases; use codex_protocol::models::ResponseItem; use pretty_assertions::assert_eq; -fn render(state: AppsInstructionsState, previous: PreviousSectionState<'_, bool>) -> Vec { - state - .render_diff(previous) - .into_iter() - .map(|fragment| fragment.render()) - .collect() -} - #[test] -fn renders_only_when_apps_become_available() { +fn snapshots() { + use PreviousSectionState::Absent; + use PreviousSectionState::Known; + use PreviousSectionState::Unknown; + let unavailable = AppsInstructionsState::new(/*available*/ false); let available = AppsInstructionsState::new(/*available*/ true); - let false_snapshot = false; - let true_snapshot = true; - assert_eq!( - render(unavailable, PreviousSectionState::Absent), - Vec::::new() - ); - assert_eq!(render(available, PreviousSectionState::Absent).len(), 1); - assert_eq!( - render(available, PreviousSectionState::Known(&false_snapshot)).len(), - 1 - ); - assert_eq!( - render(available, PreviousSectionState::Known(&true_snapshot)), - Vec::::new() - ); - assert_eq!( - render(unavailable, PreviousSectionState::Known(&true_snapshot)), - Vec::::new() - ); + insta::assert_snapshot!(render_section_cases(&[ + (Absent, Absent), + (Absent, Known(&unavailable)), + (Absent, Known(&available)), + (Known(&unavailable), Known(&available)), + (Known(&available), Known(&available)), + (Known(&available), Known(&unavailable)), + (Unknown, Known(&unavailable)), + (Unknown, Known(&available)), + ])); } #[test] diff --git a/codex-rs/core/src/context/world_state/environment_tests.rs b/codex-rs/core/src/context/world_state/environment_tests.rs index 90af017bda..3aa43abfc2 100644 --- a/codex-rs/core/src/context/world_state/environment_tests.rs +++ b/codex-rs/core/src/context/world_state/environment_tests.rs @@ -1,18 +1,18 @@ +use super::super::PreviousSectionState; +use super::super::test_support::render_section_cases; use super::*; -use crate::context::ContextualUserFragment; -use crate::context::world_state::WorldState; use anyhow::Result; use codex_exec_server::LOCAL_ENVIRONMENT_ID; -use codex_protocol::models::ContentItem; use codex_protocol::models::PermissionProfile; -use codex_protocol::models::ResponseItem; use codex_protocol::permissions::NetworkSandboxPolicy; -use pretty_assertions::assert_eq; -use serde_json::json; #[test] -fn renders_full_environment_state() -> Result<()> { - let context = EnvironmentsState { +fn snapshots() -> Result<()> { + use PreviousSectionState::Absent; + use PreviousSectionState::Known; + use PreviousSectionState::Unknown; + + let full = EnvironmentsState { environments: [ ("laptop".to_string(), available("file:///repo", "zsh")?), ( @@ -24,34 +24,7 @@ fn renders_full_environment_state() -> Result<()> { .collect(), ..Default::default() }; - - let mut world_state = WorldState::default(); - world_state.add_section(context); - - assert_eq!( - vec![user_message( - r#" - - - /workspace - bash - - - /repo - zsh - - -"#, - )], - render_fragments(world_state.render_full()), - ); - Ok(()) -} - -#[test] -fn renders_only_changed_environments() -> Result<()> { - let mut previous = WorldState::default(); - previous.add_section(EnvironmentsState { + let before_environment_changes = EnvironmentsState { environments: [ ("laptop".to_string(), available("file:///repo", "bash")?), ("devbox".to_string(), starting("file:///workspace")?), @@ -60,9 +33,8 @@ fn renders_only_changed_environments() -> Result<()> { .into_iter() .collect(), ..Default::default() - }); - let mut current = WorldState::default(); - current.add_section(EnvironmentsState { + }; + let after_environment_changes = EnvironmentsState { environments: [ ("laptop".to_string(), available("file:///repo", "zsh")?), ( @@ -74,35 +46,7 @@ fn renders_only_changed_environments() -> Result<()> { .into_iter() .collect(), ..Default::default() - }); - - assert_eq!( - vec![user_message( - r#" - - - /workspace - powershell - - - /repo - zsh - - - - /remote - starting - - -"#, - )], - render_fragments(current.render_diff(&previous.snapshot())), - ); - Ok(()) -} - -#[test] -fn persisted_turn_context_values_render_a_diff() -> Result<()> { + }; let environments = EnvironmentsState { environments: [( LOCAL_ENVIRONMENT_ID.to_string(), @@ -112,8 +56,7 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> { .collect(), ..Default::default() }; - let mut previous = WorldState::default(); - previous.add_section(EnvironmentsState { + let before_turn_context_changes = EnvironmentsState { current_date: Some("2026-06-19".to_string()), timezone: Some("UTC".to_string()), network: Some(NetworkContext::new( @@ -126,9 +69,8 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> { &[], )), ..environments.clone() - }); - let mut current = WorldState::default(); - current.add_section(EnvironmentsState { + }; + let after_turn_context_changes = EnvironmentsState { current_date: Some("2026-06-20".to_string()), timezone: Some("America/Los_Angeles".to_string()), network: Some(NetworkContext::new( @@ -143,26 +85,8 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> { &[], )), ..environments - }); - - assert_eq!( - vec![user_message( - r#" - 2026-06-20 - America/Los_Angeles - new.example.comblocked.example.com - -"#, - )], - render_fragments(current.render_diff(&previous.snapshot())), - ); - Ok(()) -} - -#[test] -fn persisted_snapshot_uses_model_visible_path_and_context_values() -> Result<()> { - let mut world_state = WorldState::default(); - world_state.add_section(EnvironmentsState { + }; + let foreign_windows = EnvironmentsState { environments: [( "remote".to_string(), available("file:///C:/windows", "powershell")?, @@ -174,29 +98,8 @@ fn persisted_snapshot_uses_model_visible_path_and_context_values() -> Result<()> &[], )), ..Default::default() - }); - - assert_eq!( - serde_json::to_value(world_state.snapshot())?, - json!({ - "environments": { - "environments": { - "remote": { - "cwd": "C:\\windows", - "status": "available", - "shell": "powershell" - } - }, - "filesystem": "" - } - }), - ); - Ok(()) -} - -#[test] -fn single_environment_diff_ignores_unknown_shell() -> Result<()> { - let previous = EnvironmentsState { + }; + let unknown_shell = EnvironmentsState { environments: [( LOCAL_ENVIRONMENT_ID.to_string(), EnvironmentState { @@ -209,7 +112,7 @@ fn single_environment_diff_ignores_unknown_shell() -> Result<()> { .collect(), ..Default::default() }; - let current = EnvironmentsState { + let known_shell = EnvironmentsState { environments: [( LOCAL_ENVIRONMENT_ID.to_string(), available("file:///repo", "zsh")?, @@ -218,21 +121,7 @@ fn single_environment_diff_ignores_unknown_shell() -> Result<()> { .collect(), ..Default::default() }; - let previous = WorldStateSection::snapshot(&previous); - - assert_eq!( - None, - render_fragment(WorldStateSection::render_diff( - ¤t, - PreviousSectionState::Known(&previous), - )) - ); - Ok(()) -} - -#[test] -fn removed_legacy_environment_renders_unavailable() -> Result<()> { - let previous = EnvironmentsState { + let legacy_environment = EnvironmentsState { environments: [( LOCAL_ENVIRONMENT_ID.to_string(), available("file:///repo", "bash")?, @@ -241,21 +130,24 @@ fn removed_legacy_environment_renders_unavailable() -> Result<()> { .collect(), ..Default::default() }; - let previous = WorldStateSection::snapshot(&previous); + let empty = EnvironmentsState::default(); - assert_eq!( - Some(user_message( - r#" - - - -"#, - )), - render_fragment(WorldStateSection::render_diff( - &EnvironmentsState::default(), - PreviousSectionState::Known(&previous), - )), - ); + insta::assert_snapshot!(render_section_cases(&[ + (Absent, Absent), + (Absent, Known(&full)), + (Unknown, Known(&full)), + ( + Known(&before_environment_changes), + Known(&after_environment_changes), + ), + ( + Known(&before_turn_context_changes), + Known(&after_turn_context_changes), + ), + (Absent, Known(&foreign_windows)), + (Known(&unknown_shell), Known(&known_shell)), + (Known(&legacy_environment), Known(&empty)), + ])); Ok(()) } @@ -274,26 +166,3 @@ fn starting(cwd: &str) -> Result { shell: None, }) } - -fn render_fragments(fragments: Vec>) -> Vec { - fragments - .into_iter() - .map(ContextualUserFragment::into_boxed_response_item) - .collect() -} - -fn render_fragment(fragment: Option>) -> Option { - fragment.map(ContextualUserFragment::into_boxed_response_item) -} - -fn user_message(text: &str) -> ResponseItem { - ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: text.to_string(), - }], - phase: None, - internal_chat_message_metadata_passthrough: None, - } -} diff --git a/codex-rs/core/src/context/world_state/mod.rs b/codex-rs/core/src/context/world_state/mod.rs index ccdc976c4c..bb77c6f3da 100644 --- a/codex-rs/core/src/context/world_state/mod.rs +++ b/codex-rs/core/src/context/world_state/mod.rs @@ -2,6 +2,8 @@ mod agents_md; mod apps_instructions; mod environment; mod plugins_instructions; +#[cfg(test)] +mod test_support; use crate::context::ContextualUserFragment; use codex_extension_api::PreviousWorldStateSection; diff --git a/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs b/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs index a688ff93ca..c9a5e1d251 100644 --- a/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs +++ b/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs @@ -1,43 +1,29 @@ use super::*; use crate::context::ContextualUserFragment; +use crate::context::world_state::PreviousSectionState; +use crate::context::world_state::test_support::render_section_cases; use codex_protocol::models::ResponseItem; use pretty_assertions::assert_eq; -fn render( - state: PluginsInstructionsState, - previous: PreviousSectionState<'_, bool>, -) -> Vec { - state - .render_diff(previous) - .into_iter() - .map(|fragment| fragment.render()) - .collect() -} - #[test] -fn renders_only_when_plugins_become_available() { +fn snapshots() { + use PreviousSectionState::Absent; + use PreviousSectionState::Known; + use PreviousSectionState::Unknown; + let unavailable = PluginsInstructionsState::new(/*available*/ false); let available = PluginsInstructionsState::new(/*available*/ true); - let false_snapshot = false; - let true_snapshot = true; - assert_eq!( - render(unavailable, PreviousSectionState::Absent), - Vec::::new() - ); - assert_eq!(render(available, PreviousSectionState::Absent).len(), 1); - assert_eq!( - render(available, PreviousSectionState::Known(&false_snapshot)).len(), - 1 - ); - assert_eq!( - render(available, PreviousSectionState::Known(&true_snapshot)), - Vec::::new() - ); - assert_eq!( - render(unavailable, PreviousSectionState::Known(&true_snapshot)), - Vec::::new() - ); + insta::assert_snapshot!(render_section_cases(&[ + (Absent, Absent), + (Absent, Known(&unavailable)), + (Absent, Known(&available)), + (Known(&unavailable), Known(&available)), + (Known(&available), Known(&available)), + (Known(&available), Known(&unavailable)), + (Unknown, Known(&unavailable)), + (Unknown, Known(&available)), + ])); } #[test] diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap new file mode 100644 index 0000000000..8aefb78ed7 --- /dev/null +++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap @@ -0,0 +1,51 @@ +--- +source: core/src/context/world_state/agents_md_tests.rs +expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&empty)),\n(Absent, Known(&project_formatter)),\n(Known(&project_formatter), Known(&project_formatter)),\n(Known(&old), Known(&new)), (Known(&new), Known(&empty)),\n(Unknown, Known(&new)), (Unknown, Known(&empty)),])" +--- +Absent -> Absent +None + +Absent -> {} +None + +Absent -> {"text":"use the project formatter"} (role - user) +# AGENTS.md instructions + + +use the project formatter + + +{"text":"use the project formatter"} -> {"text":"use the project formatter"} +None + +{"text":"old instructions"} -> {"text":"new instructions"} (role - user) +# AGENTS.md instructions + + +These AGENTS.md instructions replace all previously provided AGENTS.md instructions. + +new instructions + + +{"text":"new instructions"} -> {} (role - user) +# AGENTS.md instructions + + +The previously provided AGENTS.md instructions no longer apply. + + +Unknown -> {"text":"new instructions"} (role - user) +# AGENTS.md instructions + + +These AGENTS.md instructions replace all previously provided AGENTS.md instructions. + +new instructions + + +Unknown -> {} (role - user) +# AGENTS.md instructions + + +The previously provided AGENTS.md instructions no longer apply. + diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap new file mode 100644 index 0000000000..05e3a1c2cb --- /dev/null +++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap @@ -0,0 +1,39 @@ +--- +source: core/src/context/world_state/apps_instructions_tests.rs +expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&unavailable)),\n(Absent, Known(&available)), (Known(&unavailable), Known(&available)),\n(Known(&available), Known(&available)),\n(Known(&available), Known(&unavailable)), (Unknown, Known(&unavailable)),\n(Unknown, Known(&available)),])" +--- +Absent -> Absent +None + +Absent -> false +None + +Absent -> true (role - developer) + +## Apps (Connectors) +Apps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{connector_id})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps. +An app is equivalent to a set of MCP tools within the `codex_apps` MCP. +An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it. +Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps. + + +false -> true (role - developer) + +## Apps (Connectors) +Apps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{connector_id})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps. +An app is equivalent to a set of MCP tools within the `codex_apps` MCP. +An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it. +Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps. + + +true -> true +None + +true -> false +None + +Unknown -> false +None + +Unknown -> true +None diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap new file mode 100644 index 0000000000..60e5ba315c --- /dev/null +++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap @@ -0,0 +1,78 @@ +--- +source: core/src/context/world_state/environment_tests.rs +expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&full)),\n(Unknown, Known(&full)),\n(Known(&before_environment_changes), Known(&after_environment_changes),),\n(Known(&before_turn_context_changes), Known(&after_turn_context_changes),),\n(Absent, Known(&foreign_windows)),\n(Known(&unknown_shell), Known(&known_shell)),\n(Known(&legacy_environment), Known(&empty)),])" +--- +Absent -> Absent +None + +Absent -> {"environments":{"devbox":{"cwd":"/workspace","shell":"bash","status":"available"},"laptop":{"cwd":"/repo","shell":"zsh","status":"available"}}} (role - user) + + + + /workspace + bash + + + /repo + zsh + + + + +Unknown -> {"environments":{"devbox":{"cwd":"/workspace","shell":"bash","status":"available"},"laptop":{"cwd":"/repo","shell":"zsh","status":"available"}}} (role - user) + + + + /workspace + bash + + + /repo + zsh + + + + +{"environments":{"devbox":{"cwd":"/workspace","status":"starting"},"laptop":{"cwd":"/repo","shell":"bash","status":"available"},"old":{"cwd":"/old","shell":"sh","status":"available"}}} -> {"environments":{"devbox":{"cwd":"/workspace","shell":"powershell","status":"available"},"laptop":{"cwd":"/repo","shell":"zsh","status":"available"},"remote":{"cwd":"/remote","status":"starting"}}} (role - user) + + + + /workspace + powershell + + + /repo + zsh + + + + /remote + starting + + + + +{"current_date":"2026-06-19","environments":{"local":{"cwd":"/repo","shell":"zsh","status":"available"}},"filesystem":"","network":"old.example.com","timezone":"UTC"} -> {"current_date":"2026-06-20","environments":{"local":{"cwd":"/repo","shell":"zsh","status":"available"}},"filesystem":"","network":"new.example.comblocked.example.com","timezone":"America/Los_Angeles"} (role - user) + + 2026-06-20 + America/Los_Angeles + new.example.comblocked.example.com + + + +Absent -> {"environments":{"remote":{"cwd":"C:\\windows","shell":"powershell","status":"available"}},"filesystem":""} (role - user) + + C:\windows + powershell + + + +{"environments":{"local":{"cwd":"/repo","status":"available"}}} -> {"environments":{"local":{"cwd":"/repo","shell":"zsh","status":"available"}}} +None + +{"environments":{"local":{"cwd":"/repo","shell":"bash","status":"available"}}} -> {"environments":{}} (role - user) + + + + + diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap new file mode 100644 index 0000000000..851917d2bd --- /dev/null +++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap @@ -0,0 +1,47 @@ +--- +source: core/src/context/world_state/plugins_instructions_tests.rs +expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&unavailable)),\n(Absent, Known(&available)), (Known(&unavailable), Known(&available)),\n(Known(&available), Known(&available)),\n(Known(&available), Known(&unavailable)), (Unknown, Known(&unavailable)),\n(Unknown, Known(&available)),])" +--- +Absent -> Absent +None + +Absent -> false +None + +Absent -> true (role - developer) + +## Plugins +A plugin is a local bundle of skills, MCP servers, and apps. +### How to use plugins +- Skill naming: If a plugin contributes skills, those skill entries are prefixed with `plugin_name:` in the Skills list. +- MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as `mcp__server__tool`; use tool provenance to tell which plugin they come from. +- Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. +- Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. +- Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associated skills, MCP tools, and apps exposed elsewhere in this turn. +- Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback. + + +false -> true (role - developer) + +## Plugins +A plugin is a local bundle of skills, MCP servers, and apps. +### How to use plugins +- Skill naming: If a plugin contributes skills, those skill entries are prefixed with `plugin_name:` in the Skills list. +- MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as `mcp__server__tool`; use tool provenance to tell which plugin they come from. +- Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. +- Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. +- Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associated skills, MCP tools, and apps exposed elsewhere in this turn. +- Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback. + + +true -> true +None + +true -> false +None + +Unknown -> false +None + +Unknown -> true +None diff --git a/codex-rs/core/src/context/world_state/test_support.rs b/codex-rs/core/src/context/world_state/test_support.rs new file mode 100644 index 0000000000..62bbae4d5b --- /dev/null +++ b/codex-rs/core/src/context/world_state/test_support.rs @@ -0,0 +1,83 @@ +use super::ErasedWorldStateSection; +use super::PreviousSectionState; +use super::WorldStateSection; +use crate::context::ContextualUserFragment; + +pub(super) fn render_section_cases<'a, S: WorldStateSection>( + cases: &[(PreviousSectionState<'a, S>, PreviousSectionState<'a, S>)], +) -> String { + cases + .iter() + .map(|(before, after)| { + let rendered = render_diff(before, after); + let role = rendered.as_ref().map_or_else(String::new, |fragment| { + format!(" (role - {})", fragment.role()) + }); + let content = rendered + .as_ref() + .map_or_else(|| "None".to_string(), |fragment| fragment.render()); + format!( + "{} -> {}{role}\n{content}", + render_state(before), + render_state(after), + ) + }) + .collect::>() + .join("\n\n") +} + +fn render_state(state: &PreviousSectionState<'_, S>) -> String { + match state { + PreviousSectionState::Absent => "Absent".to_string(), + PreviousSectionState::Unknown => "Unknown".to_string(), + PreviousSectionState::Known(section) => render_snapshot(*section), + } +} + +fn render_diff( + before: &PreviousSectionState<'_, S>, + after: &PreviousSectionState<'_, S>, +) -> Option> { + let PreviousSectionState::Known(after) = after else { + return None; + }; + let previous_snapshot; + let previous = match before { + PreviousSectionState::Absent => PreviousSectionState::Absent, + PreviousSectionState::Unknown => PreviousSectionState::Unknown, + PreviousSectionState::Known(before) => { + previous_snapshot = snapshot_value(*before); + PreviousSectionState::Known(&previous_snapshot) + } + }; + ErasedWorldStateSection::render_diff(*after, previous) +} + +fn render_snapshot(section: &S) -> String { + serde_json::to_string(&sort_json(snapshot_value(section))) + .expect("world-state section snapshot should serialize") +} + +fn sort_json(value: serde_json::Value) -> serde_json::Value { + match value { + serde_json::Value::Array(values) => { + serde_json::Value::Array(values.into_iter().map(sort_json).collect()) + } + serde_json::Value::Object(values) => { + let mut values = values.into_iter().collect::>(); + values.sort_by(|(left, _), (right, _)| left.cmp(right)); + serde_json::Value::Object( + values + .into_iter() + .map(|(key, value)| (key, sort_json(value))) + .collect(), + ) + } + value => value, + } +} + +fn snapshot_value(section: &S) -> serde_json::Value { + ErasedWorldStateSection::snapshot(section) + .expect("world-state section snapshot should serialize to a non-null value") +}