unified-exec: retain PathUri in command events (#28780)

## Why

App-server must report command events containing foreign-platform paths
without changing existing client or rollout path-string formats.

## What changed

- retain `PathUri` through exec command begin/end events
- convert cwd values to `LegacyAppPathString` at the app-server
compatibility boundary
- drop command actions with foreign paths and log them
- serialize rollout-trace cwd values using their inferred native path
representation
- restore Wine coverage for retained Windows cwd values and successful
completion
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-18 05:00:04 +00:00
1 parent 285eff6c3e
commit 3931bc2bde
56 files changed
+566 -125

No files matched your search

@@ -23,6 +23,7 @@ use crate::protocol::v2::PatchApplyStatus;
use crate::protocol::v2::PatchChangeKind;
use crate::protocol::v2::ThreadItem;
use codex_protocol::ThreadId;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
use codex_protocol::protocol::ExecApprovalRequestEvent;
use codex_protocol::protocol::ExecCommandBeginEvent;
@@ -34,8 +35,11 @@ use codex_protocol::protocol::PatchApplyBeginEvent;
use codex_protocol::protocol::PatchApplyEndEvent;
use codex_shell_command::parse_command::parse_command;
use codex_shell_command::parse_command::shlex_join;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
use std::collections::HashMap;
use std::path::PathBuf;
use tracing::warn;
pub fn build_file_change_approval_request_item(
payload: &ApplyPatchApprovalRequestEvent,
@@ -69,7 +73,7 @@ pub fn build_command_execution_approval_request_item(
ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command: shlex_join(&payload.command),
cwd: payload.cwd.clone(),
cwd: payload.cwd.clone().into(),
process_id: None,
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::InProgress,
@@ -86,19 +90,15 @@ pub fn build_command_execution_approval_request_item(
}
pub fn build_command_execution_begin_item(payload: &ExecCommandBeginEvent) -> ThreadItem {
let command_actions = command_actions_for_path_uri(&payload.parsed_cmd, &payload.cwd);
ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command: shlex_join(&payload.command),
cwd: payload.cwd.clone(),
cwd: payload.cwd.clone().into(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status: CommandExecutionStatus::InProgress,
command_actions: payload
.parsed_cmd
.iter()
.cloned()
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &payload.cwd))
.collect(),
command_actions,
aggregated_output: None,
exit_code: None,
duration_ms: None,
@@ -112,26 +112,63 @@ pub fn build_command_execution_end_item(payload: &ExecCommandEndEvent) -> Thread
Some(payload.aggregated_output.clone())
};
let duration_ms = i64::try_from(payload.duration.as_millis()).unwrap_or(i64::MAX);
let command_actions = command_actions_for_path_uri(&payload.parsed_cmd, &payload.cwd);
ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command: shlex_join(&payload.command),
cwd: payload.cwd.clone(),
cwd: payload.cwd.clone().into(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status: (&payload.status).into(),
command_actions: payload
.parsed_cmd
.iter()
.cloned()
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &payload.cwd))
.collect(),
command_actions,
aggregated_output,
exit_code: Some(payload.exit_code),
duration_ms: Some(duration_ms),
}
}
fn command_actions_for_path_uri(parsed_cmd: &[ParsedCommand], cwd: &PathUri) -> Vec<CommandAction> {
// TODO(anp): Carry PathUri into CommandAction so foreign Read actions retain resolved paths.
// Until then, omit those actions rather than project a foreign cwd onto the host.
let native_cwd = if cwd.infer_path_convention() == Some(PathConvention::native()) {
cwd.to_abs_path().ok()
} else {
None
};
parsed_cmd
.iter()
.cloned()
.filter_map(|parsed| match parsed {
ParsedCommand::Read { cmd, name, path } => match native_cwd.as_ref() {
Some(native_cwd) => Some(CommandAction::Read {
command: cmd,
name,
path: native_cwd.join(path),
}),
None => {
warn!(
command = cmd,
%cwd,
"omitting read command action whose path cannot be resolved against a foreign cwd"
);
None
}
},
ParsedCommand::ListFiles { cmd, path } => {
Some(CommandAction::ListFiles { command: cmd, path })
}
ParsedCommand::Search { cmd, query, path } => Some(CommandAction::Search {
command: cmd,
query,
path,
}),
ParsedCommand::Unknown { cmd } => Some(CommandAction::Unknown { command: cmd }),
})
.collect()
}
/// Build a guardian-derived [`ThreadItem`].
///
/// Currently this only synthesizes [`ThreadItem::CommandExecution`] for
@@ -150,7 +187,7 @@ pub fn build_item_from_guardian_event(
Some(ThreadItem::CommandExecution {
id: id.clone(),
command,
cwd: cwd.clone(),
cwd: cwd.clone().into(),
process_id: None,
source: CommandExecutionSource::Agent,
status,
@@ -186,7 +223,7 @@ pub fn build_item_from_guardian_event(
Some(ThreadItem::CommandExecution {
id: id.clone(),
command,
cwd: cwd.clone(),
cwd: cwd.clone().into(),
process_id: None,
source: CommandExecutionSource::Agent,
status,
@@ -315,3 +352,7 @@ fn format_file_change_diff(change: &FileChange) -> String {
}
}
}
#[cfg(test)]
#[path = "item_builders_tests.rs"]
mod tests;