Match Windows shell guidance to the executor platform (#41368)

## What changed

- Include Windows safety guidance in the `exec_command` tool description when a
  single ready environment reports Windows as its executor platform.
- Omit that guidance when the selected executor reports a non-Windows platform.
- Preserve the host-based behavior when platform information is unavailable or
  the tool can target multiple environments.

## Testing

- Cover Windows, non-Windows, unknown-platform, and multi-environment tool plans.
- Verify remote environments expose guidance that matches their target OS.

GitOrigin-RevId: 95e09790446c684ffaff2a0be077db426fff4618
This commit is contained in:
iceweasel-oai
2026-08-28 17:09:22 +00:00
committed by copyberry
parent 7e41be641e
commit 5ed294d49d
6 changed files with 95 additions and 11 deletions
@@ -14,7 +14,10 @@ pub struct CommandToolOptions {
#[cfg(test)]
pub fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec {
create_exec_command_tool_with_environment_id(
options, /*include_environment_id*/ false, /*include_shell_parameter*/ true,
options,
/*include_environment_id*/ false,
/*include_shell_parameter*/ true,
/*include_windows_shell_guidance*/ cfg!(windows),
)
}
@@ -22,6 +25,7 @@ pub(crate) fn create_exec_command_tool_with_environment_id(
options: CommandToolOptions,
include_environment_id: bool,
include_shell_parameter: bool,
include_windows_shell_guidance: bool,
) -> ToolSpec {
let yield_time_ms_description = if cfg!(windows) {
"Maximum time to wait before returning a session ID for a still-running command. Commands that finish sooner return immediately. For ordinary commands, omit this parameter to use the 10000 ms default. Effective range on Windows is 10000-30000 ms."
@@ -90,7 +94,7 @@ pub(crate) fn create_exec_command_tool_with_environment_id(
ToolSpec::Function(ResponsesApiTool {
name: "exec_command".to_string(),
description: if cfg!(windows) {
description: if include_windows_shell_guidance {
format!(
"Runs a command in a PTY, returning output or a session ID for ongoing interaction.\n\n{}",
windows_shell_guidance()
@@ -107,6 +107,7 @@ fn exec_command_tool_can_hide_shell_parameter() {
},
/*include_environment_id*/ false,
/*include_shell_parameter*/ false,
/*include_windows_shell_guidance*/ cfg!(windows),
);
assert!(!has_parameter(&tool, "shell"));
@@ -54,6 +54,7 @@ pub(crate) struct ExecCommandHandlerOptions {
pub(crate) exec_permission_approvals_enabled: bool,
pub(crate) include_environment_id: bool,
pub(crate) include_shell_parameter: bool,
pub(crate) include_windows_shell_guidance: bool,
}
pub struct ExecCommandHandler {
@@ -68,6 +69,7 @@ impl Default for ExecCommandHandler {
exec_permission_approvals_enabled: false,
include_environment_id: false,
include_shell_parameter: true,
include_windows_shell_guidance: cfg!(windows),
},
}
}
@@ -92,6 +94,7 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
},
self.options.include_environment_id,
self.options.include_shell_parameter,
self.options.include_windows_shell_guidance,
)
}
+23
View File
@@ -1009,6 +1009,9 @@ fn add_core_tool_sources(context: &CoreToolPlanContext<'_>, registry: &mut ToolR
turn_context,
context.environments,
),
include_windows_shell_guidance: should_include_windows_shell_guidance(
context.environments,
),
}));
registry.add(WriteStdinHandler);
}
@@ -1055,6 +1058,25 @@ fn any_environment_allows_login_shell(environments: &TurnEnvironmentSnapshot) ->
.any(|environment| environment.config().allow_login_shell)
}
fn should_include_windows_shell_guidance(environments: &TurnEnvironmentSnapshot) -> bool {
let mut environments = environments.turn_environments();
let Some(environment) = environments.next() else {
return false;
};
let executor_platform_os = if environments.next().is_none() {
environment.executor_platform_os.as_deref()
} else {
None
};
// One tool schema can target any ready environment. Multi-environment turns and legacy
// executors without platform OS information preserve the host-derived guidance.
match executor_platform_os {
Some(platform_os) => platform_os == "windows",
None => cfg!(windows),
}
}
#[instrument(level = "trace", skip_all)]
fn add_shell_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistry) {
let turn_context = context.turn_context;
@@ -1079,6 +1101,7 @@ fn add_shell_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistr
turn_context,
context.environments,
),
include_windows_shell_guidance: should_include_windows_shell_guidance(context.environments),
}));
registry.add(WriteStdinHandler);
}
@@ -499,6 +499,13 @@ fn has_parameter(spec: &ToolSpec, parameter_name: &str) -> bool {
.is_some()
}
fn has_windows_shell_guidance(spec: &ToolSpec) -> bool {
let ToolSpec::Function(tool) = spec else {
return false;
};
tool.description.contains("Windows safety rules:")
}
fn apply_patch_accepts_environment_id(spec: &ToolSpec) -> bool {
match spec {
ToolSpec::Freeform(tool) if tool.name == "apply_patch" => {
@@ -899,6 +906,45 @@ async fn shell_family_registers_only_unified_exec_tools() {
assert!(has_parameter(plan.visible_spec("exec_command"), "shell"));
}
#[tokio::test]
async fn exec_command_guidance_follows_executor_platform_and_fallbacks() {
let opposite_host_os = if cfg!(windows) { "linux" } else { "windows" };
for (platform_os, multiple_environments, expect_windows_guidance) in [
(Some("windows"), false, true),
(Some("linux"), false, false),
(None, false, cfg!(windows)),
(Some(opposite_host_os), true, cfg!(windows)),
] {
let plan = probe(|turn| {
set_features(turn, &[Feature::ShellTool, Feature::UnifiedExec]);
set_feature(turn, Feature::ShellZshFork, /*enabled*/ false);
update_turn_settings_for_test(turn, |settings| {
Arc::make_mut(&mut settings.model_info).shell_type =
ConfigShellToolType::UnifiedExec;
});
let TurnEnvironmentState::Ready(environment) = turn
.environments
.environments
.first_mut()
.expect("primary environment")
else {
panic!("primary environment should be ready");
};
environment.executor_platform_os = platform_os.map(str::to_string);
if multiple_environments {
duplicate_primary_environment(turn);
}
})
.await;
assert_eq!(
has_windows_shell_guidance(plan.visible_spec("exec_command")),
expect_windows_guidance,
"unexpected guidance for executor platform {platform_os:?} with multiple_environments={multiple_environments}"
);
}
}
#[tokio::test]
async fn login_shell_parameter_follows_selected_environment() {
for guardian in [false, true] {