mirror of
https://github.com/openai/codex.git
synced 2026-03-03 13:13:18 +00:00
Compare commits
5 Commits
fix/notify
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a40f62d647 | ||
|
|
3807353071 | ||
|
|
3bc5129982 | ||
|
|
d2628e8931 | ||
|
|
ca47abc69a |
@@ -3871,6 +3871,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,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(),
|
||||
@@ -134,6 +135,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,
|
||||
@@ -149,6 +151,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(),
|
||||
@@ -172,6 +175,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(),
|
||||
@@ -210,6 +214,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
aggregated_output: StreamOutput::new(message.clone()),
|
||||
duration: Duration::ZERO,
|
||||
timed_out: false,
|
||||
os_pid: None,
|
||||
};
|
||||
session
|
||||
.send_event(
|
||||
@@ -217,6 +222,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) {
|
||||
|
||||
@@ -142,6 +142,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(),
|
||||
@@ -153,6 +154,7 @@ impl UnifiedExecProcessManager {
|
||||
cwd.clone(),
|
||||
ExecCommandSource::UnifiedExecStartup,
|
||||
Some(request.process_id.clone()),
|
||||
os_pid,
|
||||
);
|
||||
emitter.emit(event_ctx, ToolEventStage::Begin).await;
|
||||
|
||||
@@ -198,6 +200,7 @@ impl UnifiedExecProcessManager {
|
||||
request.command.clone(),
|
||||
cwd,
|
||||
Some(process_id),
|
||||
os_pid,
|
||||
Arc::clone(&transcript),
|
||||
output.clone(),
|
||||
exit,
|
||||
@@ -219,6 +222,7 @@ impl UnifiedExecProcessManager {
|
||||
cwd.clone(),
|
||||
start,
|
||||
process_id,
|
||||
os_pid,
|
||||
request.tty,
|
||||
Arc::clone(&transcript),
|
||||
)
|
||||
@@ -409,6 +413,7 @@ impl UnifiedExecProcessManager {
|
||||
cwd: PathBuf,
|
||||
started_at: Instant,
|
||||
process_id: String,
|
||||
os_pid: Option<u32>,
|
||||
tty: bool,
|
||||
transcript: Arc<tokio::sync::Mutex<HeadTailBuffer>>,
|
||||
) {
|
||||
@@ -445,6 +450,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);
|
||||
|
||||
@@ -641,6 +641,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(),
|
||||
@@ -671,6 +672,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,
|
||||
@@ -718,6 +720,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(),
|
||||
@@ -758,6 +761,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,
|
||||
@@ -802,6 +806,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(),
|
||||
@@ -831,6 +836,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,
|
||||
@@ -872,6 +878,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("."),
|
||||
|
||||
@@ -1749,6 +1749,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.
|
||||
@@ -1773,6 +1777,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.
|
||||
|
||||
@@ -1270,6 +1270,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,
|
||||
@@ -1295,6 +1296,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,
|
||||
@@ -1345,12 +1347,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,
|
||||
@@ -1624,6 +1628,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,
|
||||
@@ -4213,6 +4218,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(),
|
||||
@@ -4226,6 +4232,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,
|
||||
|
||||
@@ -1255,6 +1255,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,
|
||||
@@ -1294,12 +1295,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,
|
||||
@@ -1576,6 +1579,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,
|
||||
@@ -3797,6 +3801,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(),
|
||||
@@ -3810,6 +3815,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,
|
||||
|
||||
@@ -223,6 +223,7 @@ async fn spawn_process_with_stdin_mode(
|
||||
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