Overhaul shell detection and centralize command generation for unified exec (#6577)

This fixes command display for unified exec. All `cd`s and `ls`es are
now parsed.

<img width="452" height="237" alt="image"
src="https://github.com/user-attachments/assets/ce92d81f-f74c-485a-9b34-1eaa29290ec6"
/>

Deletes a ton of tests that were doing nothing from shell.rs.

---------

Co-authored-by: Pavel Krymets <pavel@krymets.com>
This commit is contained in:
pakrym-oai
2025-11-13 08:28:09 -08:00
committed by GitHub
Unverified
parent ba74cee6f7
commit d28e912214
11 changed files with 284 additions and 412 deletions
+4 -8
View File
@@ -64,10 +64,8 @@ impl UnifiedExecContext {
}
#[derive(Debug)]
pub(crate) struct ExecCommandRequest<'a> {
pub command: &'a str,
pub shell: &'a str,
pub login: bool,
pub(crate) struct ExecCommandRequest {
pub command: Vec<String>,
pub yield_time_ms: Option<u64>,
pub max_output_tokens: Option<usize>,
pub workdir: Option<PathBuf>,
@@ -105,7 +103,7 @@ struct SessionEntry {
session_ref: Arc<Session>,
turn_ref: Arc<TurnContext>,
call_id: String,
command: String,
command: Vec<String>,
cwd: PathBuf,
started_at: tokio::time::Instant,
}
@@ -197,9 +195,7 @@ mod tests {
.unified_exec_manager
.exec_command(
ExecCommandRequest {
command: cmd,
shell: "/bin/bash",
login: true,
command: vec!["bash".to_string(), "-lc".to_string(), cmd.to_string()],
yield_time_ms,
max_output_tokens: None,
workdir: None,
@@ -36,23 +36,17 @@ use super::truncate_output_to_tokens;
impl UnifiedExecSessionManager {
pub(crate) async fn exec_command(
&self,
request: ExecCommandRequest<'_>,
request: ExecCommandRequest,
context: &UnifiedExecContext,
) -> Result<UnifiedExecResponse, UnifiedExecError> {
let cwd = request
.workdir
.clone()
.unwrap_or_else(|| context.turn.cwd.clone());
let shell_flag = if request.login { "-lc" } else { "-c" };
let command = vec![
request.shell.to_string(),
shell_flag.to_string(),
request.command.to_string(),
];
let session = self
.open_session_with_sandbox(
command,
&request.command,
cwd.clone(),
request.with_escalated_permissions,
request.justification,
@@ -79,7 +73,7 @@ impl UnifiedExecSessionManager {
None
} else {
Some(
self.store_session(session, context, request.command, cwd.clone(), start)
self.store_session(session, context, &request.command, cwd.clone(), start)
.await,
)
};
@@ -99,7 +93,7 @@ impl UnifiedExecSessionManager {
let exit = response.exit_code.unwrap_or(-1);
Self::emit_exec_end_from_context(
context,
request.command.to_string(),
&request.command,
cwd,
response.output.clone(),
exit,
@@ -224,7 +218,7 @@ impl UnifiedExecSessionManager {
&self,
session: UnifiedExecSession,
context: &UnifiedExecContext,
command: &str,
command: &[String],
cwd: PathBuf,
started_at: Instant,
) -> i32 {
@@ -236,7 +230,7 @@ impl UnifiedExecSessionManager {
session_ref: Arc::clone(&context.session),
turn_ref: Arc::clone(&context.turn),
call_id: context.call_id.clone(),
command: command.to_string(),
command: command.to_vec(),
cwd,
started_at,
};
@@ -264,7 +258,7 @@ impl UnifiedExecSessionManager {
&entry.call_id,
None,
);
let emitter = ToolEmitter::unified_exec(entry.command, entry.cwd, true);
let emitter = ToolEmitter::unified_exec(&entry.command, entry.cwd, true);
emitter
.emit(event_ctx, ToolEventStage::Success(output))
.await;
@@ -272,7 +266,7 @@ impl UnifiedExecSessionManager {
async fn emit_exec_end_from_context(
context: &UnifiedExecContext,
command: String,
command: &[String],
cwd: PathBuf,
aggregated_output: String,
exit_code: i32,
@@ -321,7 +315,7 @@ impl UnifiedExecSessionManager {
pub(super) async fn open_session_with_sandbox(
&self,
command: Vec<String>,
command: &[String],
cwd: PathBuf,
with_escalated_permissions: Option<bool>,
justification: Option<String>,
@@ -330,7 +324,7 @@ impl UnifiedExecSessionManager {
let mut orchestrator = ToolOrchestrator::new();
let mut runtime = UnifiedExecRuntime::new(self);
let req = UnifiedExecToolRequest::new(
command,
command.to_vec(),
cwd,
create_env(&context.turn.shell_environment_policy),
with_escalated_permissions,