mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
Record exec pids in tool call spans
This commit is contained in:
@@ -3538,6 +3538,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new("Command output".to_string()),
|
||||
duration: StdDuration::from_secs(1),
|
||||
timed_out: true,
|
||||
os_pid: None,
|
||||
};
|
||||
let (_, turn_context) = make_session_and_context().await;
|
||||
|
||||
|
||||
@@ -636,6 +636,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new("aggregate detail".to_string()),
|
||||
duration: Duration::from_millis(10),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output: Box::new(output),
|
||||
@@ -652,6 +653,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new(String::new()),
|
||||
duration: Duration::from_millis(10),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output: Box::new(output),
|
||||
@@ -668,6 +670,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new(String::new()),
|
||||
duration: Duration::from_millis(8),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output: Box::new(output),
|
||||
@@ -711,6 +714,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new(String::new()),
|
||||
duration: Duration::from_millis(5),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output: Box::new(output),
|
||||
|
||||
@@ -313,6 +313,7 @@ async fn exec_windows_sandbox(
|
||||
stderr,
|
||||
aggregated_output,
|
||||
timed_out: capture.timed_out,
|
||||
os_pid: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -352,6 +353,7 @@ fn finalize_exec_result(
|
||||
aggregated_output,
|
||||
duration,
|
||||
timed_out,
|
||||
os_pid: raw_output.os_pid,
|
||||
};
|
||||
|
||||
if timed_out {
|
||||
@@ -469,6 +471,7 @@ struct RawExecToolCallOutput {
|
||||
pub stderr: StreamOutput<Vec<u8>>,
|
||||
pub aggregated_output: StreamOutput<Vec<u8>>,
|
||||
pub timed_out: bool,
|
||||
pub os_pid: Option<u32>,
|
||||
}
|
||||
|
||||
impl StreamOutput<String> {
|
||||
@@ -502,6 +505,7 @@ pub struct ExecToolCallOutput {
|
||||
pub aggregated_output: StreamOutput<String>,
|
||||
pub duration: Duration,
|
||||
pub timed_out: bool,
|
||||
pub os_pid: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for ExecToolCallOutput {
|
||||
@@ -513,6 +517,7 @@ impl Default for ExecToolCallOutput {
|
||||
aggregated_output: StreamOutput::new(String::new()),
|
||||
duration: Duration::ZERO,
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,6 +578,7 @@ async fn consume_truncated_output(
|
||||
// above, therefore `take()` should normally return `Some`. If it doesn't
|
||||
// we treat it as an exceptional I/O error
|
||||
|
||||
let os_pid = child.id();
|
||||
let stdout_reader = child.stdout.take().ok_or_else(|| {
|
||||
CodexErr::Io(io::Error::other(
|
||||
"stdout pipe was unexpectedly not available",
|
||||
@@ -680,6 +686,7 @@ async fn consume_truncated_output(
|
||||
stderr,
|
||||
aggregated_output,
|
||||
timed_out,
|
||||
os_pid,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -769,6 +776,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new(aggregated.to_string()),
|
||||
duration: Duration::from_millis(1),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -133,6 +134,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
aggregated_output: StreamOutput::new(aborted_message.clone()),
|
||||
duration: Duration::ZERO,
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let output_items = [user_shell_command_record_item(
|
||||
&raw_command,
|
||||
@@ -148,6 +150,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id,
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -171,6 +174,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: call_id.clone(),
|
||||
process_id: None,
|
||||
os_pid: output.os_pid,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -209,6 +213,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
aggregated_output: StreamOutput::new(message.clone()),
|
||||
duration: Duration::ZERO,
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
session
|
||||
.send_event(
|
||||
@@ -216,6 +221,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id,
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
command,
|
||||
cwd,
|
||||
|
||||
@@ -58,6 +58,7 @@ pub(crate) enum ToolEventFailure {
|
||||
Message(String),
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn emit_exec_command_begin(
|
||||
ctx: ToolEventCtx<'_>,
|
||||
command: &[String],
|
||||
@@ -66,21 +67,22 @@ pub(crate) async fn emit_exec_command_begin(
|
||||
source: ExecCommandSource,
|
||||
interaction_input: Option<String>,
|
||||
process_id: Option<&str>,
|
||||
os_pid: Option<u32>,
|
||||
) {
|
||||
let event = ExecCommandBeginEvent {
|
||||
call_id: ctx.call_id.to_string(),
|
||||
process_id: process_id.map(str::to_owned),
|
||||
os_pid,
|
||||
turn_id: ctx.turn.sub_id.clone(),
|
||||
command: command.to_vec(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
parsed_cmd: parsed_cmd.to_vec(),
|
||||
source,
|
||||
interaction_input,
|
||||
};
|
||||
record_exec_span_metadata(process_id, os_pid);
|
||||
ctx.session
|
||||
.send_event(
|
||||
ctx.turn,
|
||||
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: ctx.call_id.to_string(),
|
||||
process_id: process_id.map(str::to_owned),
|
||||
turn_id: ctx.turn.sub_id.clone(),
|
||||
command: command.to_vec(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
parsed_cmd: parsed_cmd.to_vec(),
|
||||
source,
|
||||
interaction_input,
|
||||
}),
|
||||
)
|
||||
.send_event(ctx.turn, EventMsg::ExecCommandBegin(event))
|
||||
.await;
|
||||
}
|
||||
// Concrete, allocation-free emitter: avoid trait objects and boxed futures.
|
||||
@@ -102,6 +104,7 @@ pub(crate) enum ToolEmitter {
|
||||
source: ExecCommandSource,
|
||||
parsed_cmd: Vec<ParsedCommand>,
|
||||
process_id: Option<String>,
|
||||
os_pid: Option<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -134,6 +137,7 @@ impl ToolEmitter {
|
||||
cwd: PathBuf,
|
||||
source: ExecCommandSource,
|
||||
process_id: Option<String>,
|
||||
os_pid: Option<u32>,
|
||||
) -> Self {
|
||||
let parsed_cmd = parse_command(command);
|
||||
Self::UnifiedExec {
|
||||
@@ -142,6 +146,7 @@ impl ToolEmitter {
|
||||
source,
|
||||
parsed_cmd,
|
||||
process_id,
|
||||
os_pid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +164,15 @@ impl ToolEmitter {
|
||||
) => {
|
||||
emit_exec_stage(
|
||||
ctx,
|
||||
ExecCommandInput::new(command, cwd.as_path(), parsed_cmd, *source, None, None),
|
||||
ExecCommandInput::new(
|
||||
command,
|
||||
cwd.as_path(),
|
||||
parsed_cmd,
|
||||
*source,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
stage,
|
||||
)
|
||||
.await;
|
||||
@@ -231,6 +244,7 @@ impl ToolEmitter {
|
||||
source,
|
||||
parsed_cmd,
|
||||
process_id,
|
||||
os_pid,
|
||||
},
|
||||
stage,
|
||||
) => {
|
||||
@@ -243,6 +257,7 @@ impl ToolEmitter {
|
||||
*source,
|
||||
None,
|
||||
process_id.as_deref(),
|
||||
*os_pid,
|
||||
),
|
||||
stage,
|
||||
)
|
||||
@@ -328,6 +343,7 @@ struct ExecCommandInput<'a> {
|
||||
source: ExecCommandSource,
|
||||
interaction_input: Option<&'a str>,
|
||||
process_id: Option<&'a str>,
|
||||
os_pid: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> ExecCommandInput<'a> {
|
||||
@@ -338,6 +354,7 @@ impl<'a> ExecCommandInput<'a> {
|
||||
source: ExecCommandSource,
|
||||
interaction_input: Option<&'a str>,
|
||||
process_id: Option<&'a str>,
|
||||
os_pid: Option<u32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
command,
|
||||
@@ -346,6 +363,7 @@ impl<'a> ExecCommandInput<'a> {
|
||||
source,
|
||||
interaction_input,
|
||||
process_id,
|
||||
os_pid,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,6 +375,7 @@ struct ExecCommandResult {
|
||||
exit_code: i32,
|
||||
duration: Duration,
|
||||
formatted_output: String,
|
||||
os_pid: Option<u32>,
|
||||
}
|
||||
|
||||
async fn emit_exec_stage(
|
||||
@@ -374,11 +393,13 @@ async fn emit_exec_stage(
|
||||
exec_input.source,
|
||||
exec_input.interaction_input.map(str::to_owned),
|
||||
exec_input.process_id,
|
||||
exec_input.os_pid,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
ToolEventStage::Success(output)
|
||||
| ToolEventStage::Failure(ToolEventFailure::Output(output)) => {
|
||||
let os_pid = exec_input.os_pid.or(output.os_pid);
|
||||
let exec_result = ExecCommandResult {
|
||||
stdout: output.stdout.text.clone(),
|
||||
stderr: output.stderr.text.clone(),
|
||||
@@ -386,6 +407,7 @@ async fn emit_exec_stage(
|
||||
exit_code: output.exit_code,
|
||||
duration: output.duration,
|
||||
formatted_output: format_exec_output_str(&output, ctx.turn.truncation_policy),
|
||||
os_pid,
|
||||
};
|
||||
emit_exec_end(ctx, exec_input, exec_result).await;
|
||||
}
|
||||
@@ -398,6 +420,7 @@ async fn emit_exec_stage(
|
||||
exit_code: -1,
|
||||
duration: Duration::ZERO,
|
||||
formatted_output: text,
|
||||
os_pid: exec_input.os_pid,
|
||||
};
|
||||
emit_exec_end(ctx, exec_input, exec_result).await;
|
||||
}
|
||||
@@ -409,29 +432,45 @@ async fn emit_exec_end(
|
||||
exec_input: ExecCommandInput<'_>,
|
||||
exec_result: ExecCommandResult,
|
||||
) {
|
||||
let event = ExecCommandEndEvent {
|
||||
call_id: ctx.call_id.to_string(),
|
||||
process_id: exec_input.process_id.map(str::to_owned),
|
||||
os_pid: exec_result.os_pid,
|
||||
turn_id: ctx.turn.sub_id.clone(),
|
||||
command: exec_input.command.to_vec(),
|
||||
cwd: exec_input.cwd.to_path_buf(),
|
||||
parsed_cmd: exec_input.parsed_cmd.to_vec(),
|
||||
source: exec_input.source,
|
||||
interaction_input: exec_input.interaction_input.map(str::to_owned),
|
||||
stdout: exec_result.stdout,
|
||||
stderr: exec_result.stderr,
|
||||
aggregated_output: exec_result.aggregated_output,
|
||||
exit_code: exec_result.exit_code,
|
||||
duration: exec_result.duration,
|
||||
formatted_output: exec_result.formatted_output,
|
||||
};
|
||||
record_exec_span_metadata(
|
||||
exec_input.process_id,
|
||||
exec_result.os_pid.or(exec_input.os_pid),
|
||||
);
|
||||
ctx.session
|
||||
.send_event(
|
||||
ctx.turn,
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: ctx.call_id.to_string(),
|
||||
process_id: exec_input.process_id.map(str::to_owned),
|
||||
turn_id: ctx.turn.sub_id.clone(),
|
||||
command: exec_input.command.to_vec(),
|
||||
cwd: exec_input.cwd.to_path_buf(),
|
||||
parsed_cmd: exec_input.parsed_cmd.to_vec(),
|
||||
source: exec_input.source,
|
||||
interaction_input: exec_input.interaction_input.map(str::to_owned),
|
||||
stdout: exec_result.stdout,
|
||||
stderr: exec_result.stderr,
|
||||
aggregated_output: exec_result.aggregated_output,
|
||||
exit_code: exec_result.exit_code,
|
||||
duration: exec_result.duration,
|
||||
formatted_output: exec_result.formatted_output,
|
||||
}),
|
||||
)
|
||||
.send_event(ctx.turn, EventMsg::ExecCommandEnd(event))
|
||||
.await;
|
||||
}
|
||||
|
||||
fn record_exec_span_metadata(process_id: Option<&str>, os_pid: Option<u32>) {
|
||||
let span = tracing::Span::current();
|
||||
if span.is_disabled() {
|
||||
return;
|
||||
}
|
||||
if let Some(process_id) = process_id {
|
||||
span.record("process_id", process_id);
|
||||
}
|
||||
if let Some(os_pid) = os_pid {
|
||||
span.record("os_pid", os_pid);
|
||||
}
|
||||
}
|
||||
|
||||
async fn emit_patch_end(
|
||||
ctx: ToolEventCtx<'_>,
|
||||
changes: HashMap<PathBuf, FileChange>,
|
||||
|
||||
@@ -6,6 +6,7 @@ use tokio_util::either::Either;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::AbortOnDropHandle;
|
||||
use tracing::Instrument;
|
||||
use tracing::field;
|
||||
use tracing::instrument;
|
||||
use tracing::trace_span;
|
||||
|
||||
@@ -66,6 +67,8 @@ impl ToolCallRuntime {
|
||||
tool_name = call.tool_name.as_str(),
|
||||
call_id = call.call_id.as_str(),
|
||||
aborted = false,
|
||||
process_id = field::Empty,
|
||||
os_pid = field::Empty,
|
||||
);
|
||||
|
||||
let handle: AbortOnDropHandle<Result<ResponseInputItem, FunctionCallError>> =
|
||||
|
||||
@@ -111,6 +111,7 @@ pub(crate) fn spawn_exit_watcher(
|
||||
command: Vec<String>,
|
||||
cwd: PathBuf,
|
||||
process_id: String,
|
||||
os_pid: Option<u32>,
|
||||
transcript: Arc<Mutex<HeadTailBuffer>>,
|
||||
started_at: Instant,
|
||||
) {
|
||||
@@ -130,6 +131,7 @@ pub(crate) fn spawn_exit_watcher(
|
||||
command,
|
||||
cwd,
|
||||
Some(process_id),
|
||||
os_pid,
|
||||
transcript,
|
||||
String::new(),
|
||||
exit_code,
|
||||
@@ -182,6 +184,7 @@ pub(crate) async fn emit_exec_end_for_unified_exec(
|
||||
command: Vec<String>,
|
||||
cwd: PathBuf,
|
||||
process_id: Option<String>,
|
||||
os_pid: Option<u32>,
|
||||
transcript: Arc<Mutex<HeadTailBuffer>>,
|
||||
fallback_output: String,
|
||||
exit_code: i32,
|
||||
@@ -195,6 +198,7 @@ pub(crate) async fn emit_exec_end_for_unified_exec(
|
||||
aggregated_output: StreamOutput::new(aggregated_output),
|
||||
duration,
|
||||
timed_out: false,
|
||||
os_pid,
|
||||
};
|
||||
let event_ctx = ToolEventCtx::new(session_ref.as_ref(), turn_ref.as_ref(), &call_id, None);
|
||||
let emitter = ToolEmitter::unified_exec(
|
||||
@@ -202,6 +206,7 @@ pub(crate) async fn emit_exec_end_for_unified_exec(
|
||||
cwd,
|
||||
ExecCommandSource::UnifiedExecStartup,
|
||||
process_id,
|
||||
os_pid,
|
||||
);
|
||||
emitter
|
||||
.emit(event_ctx, ToolEventStage::Success(output))
|
||||
|
||||
@@ -95,6 +95,10 @@ impl UnifiedExecProcess {
|
||||
self.process_handle.output_receiver()
|
||||
}
|
||||
|
||||
pub(crate) fn os_pid(&self) -> Option<u32> {
|
||||
self.process_handle.os_pid()
|
||||
}
|
||||
|
||||
pub(super) fn cancellation_token(&self) -> CancellationToken {
|
||||
self.cancellation_token.clone()
|
||||
}
|
||||
@@ -156,6 +160,7 @@ impl UnifiedExecProcess {
|
||||
exit_code,
|
||||
stderr: StreamOutput::new(text.to_string()),
|
||||
aggregated_output: StreamOutput::new(text.to_string()),
|
||||
os_pid: self.os_pid(),
|
||||
..Default::default()
|
||||
};
|
||||
if is_likely_sandbox_denied(sandbox_type, &exec_output) {
|
||||
|
||||
@@ -140,6 +140,7 @@ impl UnifiedExecProcessManager {
|
||||
};
|
||||
|
||||
let transcript = Arc::new(tokio::sync::Mutex::new(HeadTailBuffer::default()));
|
||||
let os_pid = process.os_pid();
|
||||
let event_ctx = ToolEventCtx::new(
|
||||
context.session.as_ref(),
|
||||
context.turn.as_ref(),
|
||||
@@ -151,6 +152,7 @@ impl UnifiedExecProcessManager {
|
||||
cwd.clone(),
|
||||
ExecCommandSource::UnifiedExecStartup,
|
||||
Some(request.process_id.clone()),
|
||||
os_pid,
|
||||
);
|
||||
emitter.emit(event_ctx, ToolEventStage::Begin).await;
|
||||
|
||||
@@ -196,6 +198,7 @@ impl UnifiedExecProcessManager {
|
||||
request.command.clone(),
|
||||
cwd,
|
||||
Some(process_id),
|
||||
os_pid,
|
||||
Arc::clone(&transcript),
|
||||
output.clone(),
|
||||
exit,
|
||||
@@ -217,6 +220,7 @@ impl UnifiedExecProcessManager {
|
||||
cwd.clone(),
|
||||
start,
|
||||
process_id,
|
||||
os_pid,
|
||||
Arc::clone(&transcript),
|
||||
)
|
||||
.await;
|
||||
@@ -401,6 +405,7 @@ impl UnifiedExecProcessManager {
|
||||
cwd: PathBuf,
|
||||
started_at: Instant,
|
||||
process_id: String,
|
||||
os_pid: Option<u32>,
|
||||
transcript: Arc<tokio::sync::Mutex<HeadTailBuffer>>,
|
||||
) {
|
||||
let entry = ProcessEntry {
|
||||
@@ -435,6 +440,7 @@ impl UnifiedExecProcessManager {
|
||||
command.to_vec(),
|
||||
cwd,
|
||||
process_id,
|
||||
os_pid,
|
||||
transcript,
|
||||
started_at,
|
||||
);
|
||||
|
||||
@@ -89,6 +89,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new("hi".to_string()),
|
||||
duration: Duration::from_secs(1),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let (_, turn_context) = make_session_and_context().await;
|
||||
let item = user_shell_command_record_item("echo hi", &exec_output, &turn_context);
|
||||
@@ -113,6 +114,7 @@ mod tests {
|
||||
aggregated_output: StreamOutput::new("combined output wins".to_string()),
|
||||
duration: Duration::from_millis(120),
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
let (_, turn_context) = make_session_and_context().await;
|
||||
let record = format_user_shell_command_record("false", &exec_output, &turn_context);
|
||||
|
||||
@@ -640,6 +640,7 @@ fn exec_command_end_success_produces_completed_command_item() {
|
||||
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "1".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -670,6 +671,7 @@ fn exec_command_end_success_produces_completed_command_item() {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "1".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -717,6 +719,7 @@ fn command_execution_output_delta_updates_item_progress() {
|
||||
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "delta-1".to_string(),
|
||||
process_id: Some("42".to_string()),
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -757,6 +760,7 @@ fn command_execution_output_delta_updates_item_progress() {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "delta-1".to_string(),
|
||||
process_id: Some("42".to_string()),
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -801,6 +805,7 @@ fn exec_command_end_failure_produces_failed_command_item() {
|
||||
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "2".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -830,6 +835,7 @@ fn exec_command_end_failure_produces_failed_command_item() {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "2".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -871,6 +877,7 @@ fn exec_command_end_without_begin_is_ignored() {
|
||||
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "no-begin".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command: Vec::new(),
|
||||
cwd: PathBuf::from("."),
|
||||
|
||||
@@ -1679,6 +1679,10 @@ pub struct ExecCommandBeginEvent {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub process_id: Option<String>,
|
||||
/// OS process identifier for the spawned command (when available).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub os_pid: Option<u32>,
|
||||
/// Turn ID that this command belongs to.
|
||||
pub turn_id: String,
|
||||
/// The command to be executed.
|
||||
@@ -1703,6 +1707,10 @@ pub struct ExecCommandEndEvent {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub process_id: Option<String>,
|
||||
/// OS process identifier for the spawned command (when available).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub os_pid: Option<u32>,
|
||||
/// Turn ID that this command belongs to.
|
||||
pub turn_id: String,
|
||||
/// The command that was executed.
|
||||
|
||||
@@ -891,6 +891,7 @@ fn begin_exec_with_source(
|
||||
let event = ExecCommandBeginEvent {
|
||||
call_id: call_id.to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -916,6 +917,7 @@ fn begin_unified_exec_startup(
|
||||
let event = ExecCommandBeginEvent {
|
||||
call_id: call_id.to_string(),
|
||||
process_id: Some(process_id.to_string()),
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -966,12 +968,14 @@ fn end_exec(
|
||||
source,
|
||||
interaction_input,
|
||||
process_id,
|
||||
os_pid,
|
||||
} = begin_event;
|
||||
chat.handle_codex_event(Event {
|
||||
id: call_id.clone(),
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id,
|
||||
process_id,
|
||||
os_pid,
|
||||
turn_id,
|
||||
command,
|
||||
cwd,
|
||||
@@ -1244,6 +1248,7 @@ async fn exec_end_without_begin_uses_event_command() {
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "call-orphan".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -3712,6 +3717,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "c1".into(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".into(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -3725,6 +3731,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "c1".into(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".into(),
|
||||
command,
|
||||
cwd,
|
||||
|
||||
@@ -876,6 +876,7 @@ fn begin_exec_with_source(
|
||||
let event = ExecCommandBeginEvent {
|
||||
call_id: call_id.to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -915,12 +916,14 @@ fn end_exec(
|
||||
source,
|
||||
interaction_input,
|
||||
process_id,
|
||||
os_pid,
|
||||
} = begin_event;
|
||||
chat.handle_codex_event(Event {
|
||||
id: call_id.clone(),
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id,
|
||||
process_id,
|
||||
os_pid,
|
||||
turn_id,
|
||||
command,
|
||||
cwd,
|
||||
@@ -1196,6 +1199,7 @@ async fn exec_end_without_begin_uses_event_command() {
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "call-orphan".to_string(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command,
|
||||
cwd,
|
||||
@@ -3316,6 +3320,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "c1".into(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".into(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
@@ -3329,6 +3334,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "c1".into(),
|
||||
process_id: None,
|
||||
os_pid: None,
|
||||
turn_id: "turn-1".into(),
|
||||
command,
|
||||
cwd,
|
||||
|
||||
@@ -209,6 +209,7 @@ pub async fn spawn_process(
|
||||
writer_tx,
|
||||
output_tx,
|
||||
initial_output_rx,
|
||||
Some(pid),
|
||||
Box::new(PipeChildTerminator {
|
||||
#[cfg(windows)]
|
||||
pid,
|
||||
|
||||
@@ -31,6 +31,7 @@ impl fmt::Debug for PtyHandles {
|
||||
pub struct ProcessHandle {
|
||||
writer_tx: mpsc::Sender<Vec<u8>>,
|
||||
output_tx: broadcast::Sender<Vec<u8>>,
|
||||
os_pid: Option<u32>,
|
||||
killer: StdMutex<Option<Box<dyn ChildTerminator>>>,
|
||||
reader_handle: StdMutex<Option<JoinHandle<()>>>,
|
||||
reader_abort_handles: StdMutex<Vec<AbortHandle>>,
|
||||
@@ -55,6 +56,7 @@ impl ProcessHandle {
|
||||
writer_tx: mpsc::Sender<Vec<u8>>,
|
||||
output_tx: broadcast::Sender<Vec<u8>>,
|
||||
initial_output_rx: broadcast::Receiver<Vec<u8>>,
|
||||
os_pid: Option<u32>,
|
||||
killer: Box<dyn ChildTerminator>,
|
||||
reader_handle: JoinHandle<()>,
|
||||
reader_abort_handles: Vec<AbortHandle>,
|
||||
@@ -68,6 +70,7 @@ impl ProcessHandle {
|
||||
Self {
|
||||
writer_tx,
|
||||
output_tx,
|
||||
os_pid,
|
||||
killer: StdMutex::new(Some(killer)),
|
||||
reader_handle: StdMutex::new(Some(reader_handle)),
|
||||
reader_abort_handles: StdMutex::new(reader_abort_handles),
|
||||
@@ -91,6 +94,11 @@ impl ProcessHandle {
|
||||
self.output_tx.subscribe()
|
||||
}
|
||||
|
||||
/// Process identifier reported by the underlying OS, if available.
|
||||
pub fn os_pid(&self) -> Option<u32> {
|
||||
self.os_pid
|
||||
}
|
||||
|
||||
/// True if the child process has exited.
|
||||
pub fn has_exited(&self) -> bool {
|
||||
self.exit_status.load(std::sync::atomic::Ordering::SeqCst)
|
||||
|
||||
@@ -86,6 +86,7 @@ pub async fn spawn_process(
|
||||
}
|
||||
|
||||
let mut child = pair.slave.spawn_command(command_builder)?;
|
||||
let os_pid = child.process_id();
|
||||
let killer = child.clone_killer();
|
||||
|
||||
let (writer_tx, mut writer_rx) = mpsc::channel::<Vec<u8>>(128);
|
||||
@@ -156,6 +157,7 @@ pub async fn spawn_process(
|
||||
writer_tx,
|
||||
output_tx,
|
||||
initial_output_rx,
|
||||
os_pid,
|
||||
Box::new(PtyChildTerminator { killer }),
|
||||
reader_handle,
|
||||
Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user