protocol: retain executor cwd in command events

This commit is contained in:
Adam Perry
2026-06-13 06:26:43 +00:00
parent 385e9e546a
commit 7d8a9ef36b
13 changed files with 275 additions and 85 deletions
+11 -4
View File
@@ -6,6 +6,8 @@ use codex_async_utils::CancelErr;
use codex_async_utils::OrCancelExt;
use codex_network_proxy::PROXY_ACTIVE_ENV_KEY;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
use tokio_util::sync::CancellationToken;
use tracing::error;
use uuid::Uuid;
@@ -150,6 +152,7 @@ pub(crate) async fn execute_user_shell_command(
let raw_command = command;
#[allow(deprecated)]
let cwd = turn_context.cwd.clone();
let cwd_uri = PathUri::from_abs_path(&cwd);
let parsed_cmd = parse_command(&display_command);
session
@@ -161,7 +164,8 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
started_at_ms: now_unix_timestamp_ms(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd_uri.clone(),
path_convention: PathConvention::native(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -235,7 +239,8 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
completed_at_ms: now_unix_timestamp_ms(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd_uri.clone(),
path_convention: PathConvention::native(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -260,7 +265,8 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
completed_at_ms: now_unix_timestamp_ms(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd_uri.clone(),
path_convention: PathConvention::native(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -305,7 +311,8 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
completed_at_ms: now_unix_timestamp_ms(),
command: display_command,
cwd,
cwd: cwd_uri,
path_convention: PathConvention::native(),
parsed_cmd,
source: ExecCommandSource::UserShell,
interaction_input: None,
+35 -32
View File
@@ -21,6 +21,8 @@ use codex_protocol::protocol::PatchApplyStatus;
use codex_protocol::protocol::TurnDiffEvent;
use codex_shell_command::parse_command::parse_command;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
@@ -92,28 +94,21 @@ fn tracker_update_for_known_delta<'a>(
}
}
pub(crate) async fn emit_exec_command_begin(
ctx: ToolEventCtx<'_>,
command: &[String],
cwd: &AbsolutePathBuf,
parsed_cmd: &[ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<String>,
process_id: Option<&str>,
) {
async fn emit_exec_command_begin(ctx: ToolEventCtx<'_>, exec_input: ExecCommandInput<'_>) {
ctx.session
.send_event(
ctx.turn,
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
call_id: ctx.call_id.to_string(),
process_id: process_id.map(str::to_owned),
process_id: exec_input.process_id.map(str::to_owned),
turn_id: ctx.turn.sub_id.clone(),
started_at_ms: now_unix_timestamp_ms(),
command: command.to_vec(),
cwd: cwd.clone(),
parsed_cmd: parsed_cmd.to_vec(),
source,
interaction_input,
command: exec_input.command.to_vec(),
cwd: exec_input.cwd.clone(),
path_convention: exec_input.path_convention,
parsed_cmd: exec_input.parsed_cmd.to_vec(),
source: exec_input.source,
interaction_input: exec_input.interaction_input.map(str::to_owned),
}),
)
.await;
@@ -122,7 +117,8 @@ pub(crate) async fn emit_exec_command_begin(
pub(crate) enum ToolEmitter {
Shell {
command: Vec<String>,
cwd: AbsolutePathBuf,
cwd: PathUri,
path_convention: PathConvention,
source: ExecCommandSource,
parsed_cmd: Vec<ParsedCommand>,
},
@@ -133,7 +129,8 @@ pub(crate) enum ToolEmitter {
},
UnifiedExec {
command: Vec<String>,
cwd: AbsolutePathBuf,
cwd: PathUri,
path_convention: PathConvention,
source: ExecCommandSource,
parsed_cmd: Vec<ParsedCommand>,
process_id: Option<String>,
@@ -145,7 +142,8 @@ impl ToolEmitter {
let parsed_cmd = parse_command(&command);
Self::Shell {
command,
cwd,
cwd: PathUri::from_abs_path(&cwd),
path_convention: PathConvention::native(),
source,
parsed_cmd,
}
@@ -165,7 +163,8 @@ impl ToolEmitter {
pub fn unified_exec(
command: &[String],
cwd: AbsolutePathBuf,
cwd: PathUri,
path_convention: PathConvention,
source: ExecCommandSource,
process_id: Option<String>,
) -> Self {
@@ -173,6 +172,7 @@ impl ToolEmitter {
Self::UnifiedExec {
command: command.to_vec(),
cwd,
path_convention,
source,
parsed_cmd,
process_id,
@@ -185,6 +185,7 @@ impl ToolEmitter {
Self::Shell {
command,
cwd,
path_convention,
source,
parsed_cmd,
..
@@ -194,7 +195,12 @@ impl ToolEmitter {
emit_exec_stage(
ctx,
ExecCommandInput::new(
command, cwd, parsed_cmd, *source, /*interaction_input*/ None,
command,
cwd,
*path_convention,
parsed_cmd,
*source,
/*interaction_input*/ None,
/*process_id*/ None,
),
stage,
@@ -314,6 +320,7 @@ impl ToolEmitter {
Self::UnifiedExec {
command,
cwd,
path_convention,
source,
parsed_cmd,
process_id,
@@ -325,6 +332,7 @@ impl ToolEmitter {
ExecCommandInput::new(
command,
cwd,
*path_convention,
parsed_cmd,
*source,
/*interaction_input*/ None,
@@ -432,7 +440,8 @@ impl ToolEmitter {
struct ExecCommandInput<'a> {
command: &'a [String],
cwd: &'a AbsolutePathBuf,
cwd: &'a PathUri,
path_convention: PathConvention,
parsed_cmd: &'a [ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<&'a str>,
@@ -442,7 +451,8 @@ struct ExecCommandInput<'a> {
impl<'a> ExecCommandInput<'a> {
fn new(
command: &'a [String],
cwd: &'a AbsolutePathBuf,
cwd: &'a PathUri,
path_convention: PathConvention,
parsed_cmd: &'a [ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<&'a str>,
@@ -451,6 +461,7 @@ impl<'a> ExecCommandInput<'a> {
Self {
command,
cwd,
path_convention,
parsed_cmd,
source,
interaction_input,
@@ -476,16 +487,7 @@ async fn emit_exec_stage(
) {
match stage {
ToolEventStage::Begin => {
emit_exec_command_begin(
ctx,
exec_input.command,
exec_input.cwd,
exec_input.parsed_cmd,
exec_input.source,
exec_input.interaction_input.map(str::to_owned),
exec_input.process_id,
)
.await;
emit_exec_command_begin(ctx, exec_input).await;
}
ToolEventStage::Success { output, .. }
| ToolEventStage::Failure(ToolEventFailure::Output(output)) => {
@@ -548,6 +550,7 @@ async fn emit_exec_end(
completed_at_ms: now_unix_timestamp_ms(),
command: exec_input.command.to_vec(),
cwd: exec_input.cwd.clone(),
path_convention: exec_input.path_convention,
parsed_cmd: exec_input.parsed_cmd.to_vec(),
source: exec_input.source,
interaction_input: exec_input.interaction_input.map(str::to_owned),
@@ -237,6 +237,7 @@ impl ExecCommandHandler {
max_output_tokens,
cwd,
cwd_uri,
path_convention: turn_environment.path_convention(),
sandbox_cwd,
environment,
shell_mode,
@@ -350,6 +351,7 @@ impl ExecCommandHandler {
max_output_tokens,
cwd,
cwd_uri,
path_convention: turn_environment.path_convention(),
sandbox_cwd,
environment,
shell_mode,
@@ -22,7 +22,8 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandOutputDeltaEvent;
use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::ExecOutputStream;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
pub(crate) const TRAILING_OUTPUT_GRACE: Duration = Duration::from_millis(100);
@@ -110,7 +111,8 @@ pub(crate) fn spawn_exit_watcher(
turn_ref: Arc<TurnContext>,
call_id: String,
command: Vec<String>,
cwd: AbsolutePathBuf,
cwd: PathUri,
path_convention: PathConvention,
process_id: i32,
transcript: Arc<Mutex<HeadTailBuffer>>,
started_at: Instant,
@@ -130,6 +132,7 @@ pub(crate) fn spawn_exit_watcher(
call_id,
command,
cwd,
path_convention,
Some(process_id.to_string()),
transcript,
String::new(),
@@ -145,6 +148,7 @@ pub(crate) fn spawn_exit_watcher(
call_id,
command,
cwd,
path_convention,
Some(process_id.to_string()),
transcript,
String::new(),
@@ -197,7 +201,8 @@ pub(crate) async fn emit_exec_end_for_unified_exec(
turn_ref: Arc<TurnContext>,
call_id: String,
command: Vec<String>,
cwd: AbsolutePathBuf,
cwd: PathUri,
path_convention: PathConvention,
process_id: Option<String>,
transcript: Arc<Mutex<HeadTailBuffer>>,
fallback_output: String,
@@ -222,6 +227,7 @@ pub(crate) async fn emit_exec_end_for_unified_exec(
let emitter = ToolEmitter::unified_exec(
&command,
cwd,
path_convention,
ExecCommandSource::UnifiedExecStartup,
process_id,
);
@@ -242,7 +248,8 @@ pub(crate) async fn emit_failed_exec_end_for_unified_exec(
turn_ref: Arc<TurnContext>,
call_id: String,
command: Vec<String>,
cwd: AbsolutePathBuf,
cwd: PathUri,
path_convention: PathConvention,
process_id: Option<String>,
transcript: Arc<Mutex<HeadTailBuffer>>,
fallback_output: String,
@@ -276,6 +283,7 @@ pub(crate) async fn emit_failed_exec_end_for_unified_exec(
let emitter = ToolEmitter::unified_exec(
&command,
cwd,
path_convention,
ExecCommandSource::UnifiedExecStartup,
process_id,
);
+3 -1
View File
@@ -33,6 +33,7 @@ use codex_protocol::models::AdditionalPermissionProfile;
use codex_tools::UnifiedExecShellMode;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_output_truncation::TruncationPolicy;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
use rand::Rng;
use rand::rng;
@@ -97,9 +98,10 @@ pub(crate) struct ExecCommandRequest {
pub environment_id: String,
pub yield_time_ms: u64,
pub max_output_tokens: Option<usize>,
/// App-host-compatible cwd retained for approval and event compatibility.
/// App-host-compatible cwd retained for local sandbox and approval handling.
pub cwd: AbsolutePathBuf,
pub cwd_uri: PathUri,
pub path_convention: PathConvention,
pub sandbox_cwd: Option<AbsolutePathBuf>,
pub environment: Arc<Environment>,
pub shell_mode: UnifiedExecShellMode,
@@ -58,6 +58,8 @@ use codex_protocol::protocol::ExecCommandSource;
use codex_tools::ToolName;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_output_truncation::approx_token_count;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
const UNIFIED_EXEC_ENV: [(&str, &str); 10] = [
("NO_COLOR", "1"),
@@ -337,7 +339,6 @@ async fn emit_failed_initial_exec_end_if_unstored(
process_started_alive: bool,
context: &UnifiedExecContext,
request: &ExecCommandRequest,
cwd: AbsolutePathBuf,
transcript: Arc<tokio::sync::Mutex<HeadTailBuffer>>,
fallback_output: String,
message: String,
@@ -352,7 +353,8 @@ async fn emit_failed_initial_exec_end_if_unstored(
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
cwd,
request.cwd_uri.clone(),
request.path_convention,
Some(request.process_id.to_string()),
transcript,
fallback_output,
@@ -459,7 +461,8 @@ impl UnifiedExecProcessManager {
);
let emitter = ToolEmitter::unified_exec(
&request.command,
cwd.clone(),
request.cwd_uri.clone(),
request.path_convention,
ExecCommandSource::UnifiedExecStartup,
Some(request.process_id.to_string()),
);
@@ -478,6 +481,8 @@ impl UnifiedExecProcessManager {
&request.command,
request.hook_command.clone(),
cwd.clone(),
request.cwd_uri.clone(),
request.path_convention,
start,
request.process_id,
request.tty,
@@ -536,7 +541,6 @@ impl UnifiedExecProcessManager {
process_started_alive,
context,
&request,
cwd.clone(),
Arc::clone(&transcript),
text.clone(),
message.clone(),
@@ -556,7 +560,6 @@ impl UnifiedExecProcessManager {
process_started_alive,
context,
&request,
cwd.clone(),
Arc::clone(&transcript),
text.clone(),
message.clone(),
@@ -608,7 +611,6 @@ impl UnifiedExecProcessManager {
process_started_alive,
context,
&request,
cwd.clone(),
Arc::clone(&transcript),
text.clone(),
message.clone(),
@@ -625,7 +627,8 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
cwd.clone(),
request.cwd_uri.clone(),
request.path_convention,
Some(process_id.to_string()),
Arc::clone(&transcript),
text.clone(),
@@ -885,6 +888,8 @@ impl UnifiedExecProcessManager {
command: &[String],
hook_command: String,
cwd: AbsolutePathBuf,
cwd_uri: PathUri,
path_convention: PathConvention,
started_at: Instant,
process_id: i32,
tty: bool,
@@ -923,7 +928,8 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
command.to_vec(),
cwd,
cwd_uri,
path_convention,
process_id,
transcript,
started_at,
@@ -296,6 +296,7 @@ async fn failed_initial_end_for_unstored_process_uses_fallback_output() {
#[allow(deprecated)]
cwd: turn.cwd.clone(),
cwd_uri: turn_environment.cwd_uri().clone(),
path_convention: turn_environment.path_convention(),
#[allow(deprecated)]
sandbox_cwd: Some(turn.cwd.clone()),
environment: turn
@@ -322,8 +323,6 @@ async fn failed_initial_end_for_unstored_process_uses_fallback_output() {
/*process_started_alive*/ false,
&context,
&request,
#[allow(deprecated)]
turn.cwd.clone(),
transcript,
"PRE_DENIAL_MARKER".to_string(),
"Network access denied".to_string(),
@@ -345,6 +344,8 @@ async fn failed_initial_end_for_unstored_process_uses_fallback_output() {
);
assert_eq!(end_event.exit_code, -1);
assert_eq!(end_event.process_id.as_deref(), Some("123"));
assert_eq!(end_event.cwd, request.cwd_uri);
assert_eq!(end_event.path_convention, request.path_convention);
assert_eq!(
end_event.aggregated_output,
"PRE_DENIAL_MARKER\nNetwork access denied"