Support configurable timeouts for thread shell commands (#41384)

## What changed

- Add an optional `timeoutMs` parameter to `thread/shellCommand` and propagate it to user shell execution.
- Preserve the one-hour default when the parameter is omitted or `null`, allow longer deadlines, and treat `0` as an immediate timeout.
- Reject negative or otherwise invalid values before execution. Timing out an auxiliary shell command leaves its active turn running.

## Testing

- Cover default, extended, immediate, invalid, standalone, active-turn, and interruption behavior.

GitOrigin-RevId: d72f67793d9766b7383f28d30140270a4a76a495
This commit is contained in:
gero-oai authored and copyberry committed 2026-08-28 18:07:40 +00:00
1 parent 4ee04c0aa5
commit e4d0ba4e92
20 files changed
+475 -45

No files matched your search

+18 -5
View File
@@ -95,7 +95,12 @@ pub async fn inter_agent_communication(
}
}
pub async fn run_user_shell_command(sess: &Arc<Session>, sub_id: String, command: String) {
pub async fn run_user_shell_command(
sess: &Arc<Session>,
sub_id: String,
command: String,
timeout_ms: Option<u64>,
) {
if let Some((turn_context, cancellation_token)) =
sess.active_turn_context_and_cancellation_token().await
{
@@ -105,6 +110,7 @@ pub async fn run_user_shell_command(sess: &Arc<Session>, sub_id: String, command
session,
turn_context,
command,
timeout_ms,
cancellation_token,
UserShellCommandMode::ActiveTurnAuxiliary,
)
@@ -116,8 +122,12 @@ pub async fn run_user_shell_command(sess: &Arc<Session>, sub_id: String, command
let turn_context = sess
.new_turn_with_default_settings(sub_id, Default::default())
.await;
sess.spawn_task(turn_context, Vec::new(), UserShellCommandTask::new(command))
.await;
sess.spawn_task(
turn_context,
Vec::new(),
UserShellCommandTask::new(command, timeout_ms),
)
.await;
}
pub async fn resolve_elicitation(
@@ -675,8 +685,11 @@ pub(super) async fn submission_loop(
set_thread_memory_mode(&sess, sub.id.clone(), mode).await;
false
}
Op::RunUserShellCommand { command } => {
run_user_shell_command(&sess, sub.id.clone(), command).await;
Op::RunUserShellCommand {
command,
timeout_ms,
} => {
run_user_shell_command(&sess, sub.id.clone(), command, timeout_ms).await;
false
}
Op::ResolveElicitation {
+9 -2
View File
@@ -1549,6 +1549,7 @@ async fn user_shell_commands_do_not_inherit_managed_network_proxy() -> anyhow::R
Arc::clone(&session),
turn_context,
command,
/*timeout_ms*/ None,
CancellationToken::new(),
UserShellCommandMode::StandaloneTurn,
)
@@ -1583,6 +1584,7 @@ async fn user_shell_commands_remain_login_shells_when_model_login_shells_are_dis
Arc::clone(&session),
turn_context,
command,
/*timeout_ms*/ None,
CancellationToken::new(),
UserShellCommandMode::StandaloneTurn,
)
@@ -10568,8 +10570,13 @@ async fn run_user_shell_command_does_not_set_reference_context_item() {
state.set_reference_context_item(/*item*/ None);
}
handlers::run_user_shell_command(&session, "sub-id".to_string(), "echo shell".to_string())
.await;
handlers::run_user_shell_command(
&session,
"sub-id".to_string(),
"echo shell".to_string(),
/*timeout_ms*/ None,
)
.await;
let deadline = StdDuration::from_secs(15);
let start = std::time::Instant::now();