mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
core: render remote environment cwd natively (#28152)
## Why Model-visible `<environment_context>` should match the environment of the executor, not of the app server. Stacked on #28146. ## What - Keep selected environment cwd values as `PathUri` while building environment context. - Render cwd text using the path convention represented by the URI, with the canonical URI as a fallback. - Preserve compatibility with legacy `TurnContextItem.cwd` values when reconstructing and diffing context. - Extend the Wine-backed remote Windows test to assert that the model sees `powershell` and `C:\windows`.
This commit is contained in:
@@ -1621,7 +1621,7 @@ impl Session {
|
||||
&self,
|
||||
reference_context_item: Option<&TurnContextItem>,
|
||||
current_context: &TurnContext,
|
||||
) -> Vec<ResponseItem> {
|
||||
) -> CodexResult<Vec<ResponseItem>> {
|
||||
// TODO: Make context updates a pure diff of persisted previous/current TurnContextItem
|
||||
// state so replay/backtracking is deterministic. Runtime inputs that affect model-visible
|
||||
// context (shell, exec policy, feature gates, previous-turn bridge) should be persisted
|
||||
@@ -1632,13 +1632,15 @@ impl Session {
|
||||
};
|
||||
let shell = self.user_shell();
|
||||
let exec_policy = self.services.exec_policy.current();
|
||||
crate::context_manager::updates::build_settings_update_items(
|
||||
reference_context_item,
|
||||
previous_turn_settings.as_ref(),
|
||||
current_context,
|
||||
shell.as_ref(),
|
||||
exec_policy.as_ref(),
|
||||
self.features.enabled(Feature::Personality),
|
||||
Ok(
|
||||
crate::context_manager::updates::build_settings_update_items(
|
||||
reference_context_item,
|
||||
previous_turn_settings.as_ref(),
|
||||
current_context,
|
||||
shell.as_ref(),
|
||||
exec_policy.as_ref(),
|
||||
self.features.enabled(Feature::Personality),
|
||||
)?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3186,7 +3188,7 @@ impl Session {
|
||||
pub(crate) async fn record_context_updates_and_set_reference_context_item(
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
) {
|
||||
) -> CodexResult<()> {
|
||||
let reference_context_item = {
|
||||
let state = self.state.lock().await;
|
||||
state.reference_context_item()
|
||||
@@ -3197,7 +3199,7 @@ impl Session {
|
||||
} else {
|
||||
// Steady-state path: append only context diffs to minimize token overhead.
|
||||
self.build_settings_update_items(reference_context_item.as_ref(), turn_context)
|
||||
.await
|
||||
.await?
|
||||
};
|
||||
let turn_context_item = turn_context.to_turn_context_item();
|
||||
if !context_items.is_empty() {
|
||||
@@ -3213,6 +3215,7 @@ impl Session {
|
||||
// context items. This keeps later runtime diffing aligned with the current turn state.
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_reference_context_item(Some(turn_context_item));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_token_usage_info(
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_protocol::protocol::CompactedItem;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::ResumedHistory;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -88,7 +89,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -128,7 +129,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
let mut previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -989,7 +990,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -1071,7 +1072,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
serde_json::to_value(Some(TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -1101,7 +1102,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -1223,7 +1224,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
let current_context_item = TurnContextItem {
|
||||
turn_id: Some(current_turn_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -1343,7 +1344,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -1506,7 +1507,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
|
||||
@@ -1874,7 +1874,8 @@ async fn resumed_history_injects_initial_context_on_first_context_update_only()
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
expected.extend(initial_context);
|
||||
let history_after_seed = session.clone_history().await;
|
||||
@@ -1882,7 +1883,8 @@ async fn resumed_history_injects_initial_context_on_first_context_update_only()
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
let history_after_second_seed = session.clone_history().await;
|
||||
assert_eq!(
|
||||
history_after_seed.raw_items(),
|
||||
@@ -2673,7 +2675,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
let previous_context_item = TurnContextItem {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
cwd: PathUri::from_abs_path(&turn_context.cwd),
|
||||
workspace_roots: None,
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
@@ -7290,7 +7292,8 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes(
|
||||
let reference_context_item = previous_context.to_turn_context_item();
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
|
||||
let environment_update = user_input_texts(&update_items)
|
||||
.into_iter()
|
||||
@@ -7360,7 +7363,8 @@ async fn build_settings_update_items_emits_environment_item_for_time_changes() {
|
||||
let reference_context_item = previous_context.to_turn_context_item();
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
|
||||
let environment_update = user_input_texts(&update_items)
|
||||
.into_iter()
|
||||
@@ -7388,7 +7392,8 @@ async fn build_settings_update_items_omits_environment_item_when_disabled() {
|
||||
let reference_context_item = previous_context.to_turn_context_item();
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
|
||||
let user_texts = user_input_texts(&update_items);
|
||||
assert!(
|
||||
@@ -7416,7 +7421,8 @@ async fn build_settings_update_items_emits_realtime_start_when_session_becomes_l
|
||||
Some(&previous_context.to_turn_context_item()),
|
||||
¤t_context,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
|
||||
let developer_texts = developer_input_texts(&update_items);
|
||||
assert!(
|
||||
@@ -7444,7 +7450,8 @@ async fn build_settings_update_items_emits_realtime_end_when_session_stops_being
|
||||
Some(&previous_context.to_turn_context_item()),
|
||||
¤t_context,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
|
||||
let developer_texts = developer_input_texts(&update_items);
|
||||
assert!(
|
||||
@@ -7478,7 +7485,8 @@ async fn build_settings_update_items_uses_previous_turn_settings_for_realtime_en
|
||||
.await;
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&previous_context_item), ¤t_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
|
||||
let developer_texts = developer_input_texts(&update_items);
|
||||
assert!(
|
||||
@@ -8130,6 +8138,21 @@ async fn turn_context_item_uses_turn_context_comp_hash_snapshot() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_context_item_stores_primary_environment_cwd_uri() {
|
||||
let (_session, mut turn_context) = make_session_and_context().await;
|
||||
let environment = turn_context.environments.turn_environments[0].clone();
|
||||
let cwd = PathUri::parse("file:///C:/windows").expect("Windows cwd URI");
|
||||
turn_context.environments.turn_environments[0] = TurnEnvironment::new(
|
||||
"remote".to_string(),
|
||||
environment.environment,
|
||||
cwd.clone(),
|
||||
environment.shell,
|
||||
);
|
||||
|
||||
assert_eq!(turn_context.to_turn_context_item().cwd, cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy() {
|
||||
let (_session, turn_context) = make_session_and_context().await;
|
||||
@@ -8171,7 +8194,8 @@ async fn record_context_updates_and_set_reference_context_item_injects_full_cont
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
let history = session.clone_history().await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
assert_eq!(history.raw_items().to_vec(), initial_context);
|
||||
@@ -8202,7 +8226,8 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
.await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(/*item*/ None);
|
||||
@@ -8216,7 +8241,8 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
|
||||
let history = session.clone_history().await;
|
||||
let mut expected_history = vec![compacted_summary];
|
||||
@@ -8246,12 +8272,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
|
||||
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&previous_context_item), &turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("settings updates should hydrate");
|
||||
assert_eq!(update_items, Vec::new());
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
|
||||
assert_eq!(
|
||||
session.clone_history().await.raw_items().to_vec(),
|
||||
@@ -8298,7 +8326,8 @@ async fn record_context_updates_and_set_reference_context_item_persists_split_fi
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
session.ensure_rollout_materialized().await;
|
||||
session.flush_rollout().await.expect("rollout should flush");
|
||||
|
||||
@@ -8382,7 +8411,8 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
|
||||
.await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.await;
|
||||
.await
|
||||
.expect("context updates should hydrate");
|
||||
session.ensure_rollout_materialized().await;
|
||||
session.flush_rollout().await.expect("rollout should flush");
|
||||
|
||||
|
||||
@@ -159,8 +159,16 @@ pub(crate) async fn run_turn(
|
||||
return None;
|
||||
}
|
||||
|
||||
sess.record_context_updates_and_set_reference_context_item(turn_context.as_ref())
|
||||
.await;
|
||||
if let Err(err) = sess
|
||||
.record_context_updates_and_set_reference_context_item(turn_context.as_ref())
|
||||
.await
|
||||
{
|
||||
let error = err.to_codex_protocol_error();
|
||||
sess.emit_turn_error_lifecycle(turn_context.as_ref(), error.clone())
|
||||
.await;
|
||||
error!(%err, "failed to hydrate persisted turn context");
|
||||
return None;
|
||||
}
|
||||
|
||||
let (injection_items, explicitly_enabled_connectors) =
|
||||
build_skills_and_plugins(&sess, turn_context.as_ref(), &input, &cancellation_token).await?;
|
||||
|
||||
@@ -396,10 +396,17 @@ impl TurnContext {
|
||||
|
||||
pub(crate) fn to_turn_context_item(&self) -> TurnContextItem {
|
||||
let workspace_roots = self.config.effective_workspace_roots();
|
||||
let cwd = self
|
||||
.environments
|
||||
.primary()
|
||||
.map(|environment| environment.cwd().clone())
|
||||
.unwrap_or_else(|| {
|
||||
#[allow(deprecated)]
|
||||
PathUri::from_abs_path(&self.cwd)
|
||||
});
|
||||
TurnContextItem {
|
||||
turn_id: Some(self.sub_id.clone()),
|
||||
#[allow(deprecated)]
|
||||
cwd: self.cwd.to_path_buf(),
|
||||
cwd,
|
||||
workspace_roots: (!workspace_roots.is_empty()).then_some(workspace_roots),
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
|
||||
Reference in New Issue
Block a user