mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(core): report os and tool info
This commit is contained in:
@@ -30,10 +30,8 @@ impl DebugLogger {
|
||||
});
|
||||
}
|
||||
|
||||
let log_dir = dirs::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join(".codex")
|
||||
.join("debug_logs");
|
||||
let mut log_dir = crate::config::find_codex_home()?;
|
||||
log_dir.push("debug_logs");
|
||||
|
||||
fs::create_dir_all(&log_dir)?;
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use os_info::Type as OsType;
|
||||
use os_info::Version;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum_macros::Display as DeriveDisplay;
|
||||
use which::which;
|
||||
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
@@ -27,9 +30,80 @@ pub(crate) struct EnvironmentContext {
|
||||
pub sandbox_mode: Option<SandboxMode>,
|
||||
pub network_access: Option<NetworkAccess>,
|
||||
pub writable_roots: Option<Vec<PathBuf>>,
|
||||
pub operating_system: Option<OperatingSystemInfo>,
|
||||
pub common_tools: Option<Vec<String>>,
|
||||
pub shell: Option<Shell>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) struct OperatingSystemInfo {
|
||||
pub family: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub architecture: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ToolCandidate {
|
||||
pub label: &'static str,
|
||||
pub detection_names: &'static [&'static str],
|
||||
}
|
||||
|
||||
pub const TOOL_CANDIDATES: &[ToolCandidate] = &[
|
||||
ToolCandidate { label: "git", detection_names: &["git"] },
|
||||
ToolCandidate { label: "gh", detection_names: &["gh"] },
|
||||
ToolCandidate { label: "rg", detection_names: &["rg"] },
|
||||
ToolCandidate { label: "fd", detection_names: &["fd", "fdfind"] },
|
||||
ToolCandidate { label: "fzf", detection_names: &["fzf"] },
|
||||
ToolCandidate { label: "jq", detection_names: &["jq"] },
|
||||
ToolCandidate { label: "yq", detection_names: &["yq"] },
|
||||
ToolCandidate { label: "sed", detection_names: &["sed"] },
|
||||
ToolCandidate { label: "awk", detection_names: &["awk"] },
|
||||
ToolCandidate { label: "xargs", detection_names: &["xargs"] },
|
||||
ToolCandidate { label: "parallel", detection_names: &["parallel"] },
|
||||
ToolCandidate { label: "curl", detection_names: &["curl"] },
|
||||
ToolCandidate { label: "wget", detection_names: &["wget"] },
|
||||
ToolCandidate { label: "tar", detection_names: &["tar"] },
|
||||
ToolCandidate { label: "unzip", detection_names: &["unzip"] },
|
||||
ToolCandidate { label: "gzip", detection_names: &["gzip"] },
|
||||
ToolCandidate { label: "zstd", detection_names: &["zstd"] },
|
||||
ToolCandidate { label: "make", detection_names: &["make"] },
|
||||
ToolCandidate { label: "just", detection_names: &["just"] },
|
||||
ToolCandidate { label: "node", detection_names: &["node"] },
|
||||
ToolCandidate { label: "npm", detection_names: &["npm"] },
|
||||
ToolCandidate { label: "pnpm", detection_names: &["pnpm"] },
|
||||
ToolCandidate { label: "python3", detection_names: &["python3"] },
|
||||
ToolCandidate { label: "pipx", detection_names: &["pipx"] },
|
||||
ToolCandidate { label: "go", detection_names: &["go"] },
|
||||
ToolCandidate { label: "rustup", detection_names: &["rustup"] },
|
||||
ToolCandidate { label: "cargo", detection_names: &["cargo"] },
|
||||
ToolCandidate { label: "rustc", detection_names: &["rustc"] },
|
||||
ToolCandidate { label: "shellcheck", detection_names: &["shellcheck"] },
|
||||
ToolCandidate { label: "shfmt", detection_names: &["shfmt"] },
|
||||
ToolCandidate { label: "docker", detection_names: &["docker"] },
|
||||
ToolCandidate { label: "docker compose", detection_names: &["docker", "docker-compose"] },
|
||||
ToolCandidate { label: "sqlite3", detection_names: &["sqlite3"] },
|
||||
ToolCandidate { label: "duckdb", detection_names: &["duckdb"] },
|
||||
ToolCandidate { label: "rsync", detection_names: &["rsync"] },
|
||||
ToolCandidate { label: "openssl", detection_names: &["openssl"] },
|
||||
ToolCandidate { label: "ssh", detection_names: &["ssh"] },
|
||||
ToolCandidate { label: "dig", detection_names: &["dig"] },
|
||||
ToolCandidate { label: "nc", detection_names: &["nc", "netcat"] },
|
||||
ToolCandidate { label: "lsof", detection_names: &["lsof"] },
|
||||
ToolCandidate { label: "ripgrep-all", detection_names: &["ripgrep-all", "rga"] },
|
||||
ToolCandidate { label: "entr", detection_names: &["entr"] },
|
||||
ToolCandidate { label: "watchexec", detection_names: &["watchexec"] },
|
||||
ToolCandidate { label: "hyperfine", detection_names: &["hyperfine"] },
|
||||
ToolCandidate { label: "pv", detection_names: &["pv"] },
|
||||
ToolCandidate { label: "bat", detection_names: &["bat"] },
|
||||
ToolCandidate { label: "delta", detection_names: &["delta"] },
|
||||
ToolCandidate { label: "tree", detection_names: &["tree"] },
|
||||
ToolCandidate { label: "python", detection_names: &["python"] },
|
||||
ToolCandidate { label: "deno", detection_names: &["deno"] },
|
||||
ToolCandidate { label: "bun", detection_names: &["bun"] },
|
||||
ToolCandidate { label: "js", detection_names: &["js"] },
|
||||
];
|
||||
|
||||
impl EnvironmentContext {
|
||||
pub fn new(
|
||||
cwd: Option<PathBuf>,
|
||||
@@ -68,6 +142,8 @@ impl EnvironmentContext {
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
operating_system: detect_operating_system_info(),
|
||||
common_tools: detect_common_tools(),
|
||||
shell,
|
||||
}
|
||||
}
|
||||
@@ -83,6 +159,8 @@ impl EnvironmentContext {
|
||||
sandbox_mode,
|
||||
network_access,
|
||||
writable_roots,
|
||||
operating_system,
|
||||
common_tools,
|
||||
// should compare all fields except shell
|
||||
shell: _,
|
||||
} = other;
|
||||
@@ -92,6 +170,8 @@ impl EnvironmentContext {
|
||||
&& self.sandbox_mode == *sandbox_mode
|
||||
&& self.network_access == *network_access
|
||||
&& self.writable_roots == *writable_roots
|
||||
&& self.operating_system == *operating_system
|
||||
&& self.common_tools == *common_tools
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +190,12 @@ impl EnvironmentContext {
|
||||
/// <sandbox_mode>...</sandbox_mode>
|
||||
/// <writable_roots>...</writable_roots>
|
||||
/// <network_access>...</network_access>
|
||||
/// <operating_system>
|
||||
/// <family>...</family>
|
||||
/// <version>...</version>
|
||||
/// <architecture>...</architecture>
|
||||
/// </operating_system>
|
||||
/// <common_tools>...</common_tools>
|
||||
/// <shell>...</shell>
|
||||
/// </environment_context>
|
||||
/// ```
|
||||
@@ -141,6 +227,28 @@ impl EnvironmentContext {
|
||||
}
|
||||
lines.push(" </writable_roots>".to_string());
|
||||
}
|
||||
if let Some(operating_system) = self.operating_system {
|
||||
lines.push(" <operating_system>".to_string());
|
||||
if let Some(family) = operating_system.family {
|
||||
lines.push(format!(" <family>{family}</family>"));
|
||||
}
|
||||
if let Some(version) = operating_system.version {
|
||||
lines.push(format!(" <version>{version}</version>"));
|
||||
}
|
||||
if let Some(architecture) = operating_system.architecture {
|
||||
lines.push(format!(" <architecture>{architecture}</architecture>"));
|
||||
}
|
||||
lines.push(" </operating_system>".to_string());
|
||||
}
|
||||
if let Some(common_tools) = self.common_tools {
|
||||
if !common_tools.is_empty() {
|
||||
lines.push(" <common_tools>".to_string());
|
||||
for tool in common_tools {
|
||||
lines.push(format!(" <tool>{tool}</tool>"));
|
||||
}
|
||||
lines.push(" </common_tools>".to_string());
|
||||
}
|
||||
}
|
||||
if let Some(shell) = self.shell
|
||||
&& let Some(shell_name) = shell.name()
|
||||
{
|
||||
@@ -163,6 +271,67 @@ impl From<EnvironmentContext> for ResponseItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_operating_system_info() -> Option<OperatingSystemInfo> {
|
||||
let info = os_info::get();
|
||||
let family = match info.os_type() {
|
||||
OsType::Unknown => None,
|
||||
other => Some(other.to_string()),
|
||||
};
|
||||
let version = match info.version() {
|
||||
Version::Unknown => None,
|
||||
other => {
|
||||
let text = other.to_string();
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(text)
|
||||
}
|
||||
}
|
||||
};
|
||||
let architecture = {
|
||||
let arch = std::env::consts::ARCH;
|
||||
if arch.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(arch.to_string())
|
||||
}
|
||||
};
|
||||
|
||||
if family.is_none() && version.is_none() && architecture.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(OperatingSystemInfo {
|
||||
family,
|
||||
version,
|
||||
architecture,
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_common_tools() -> Option<Vec<String>> {
|
||||
let mut available = Vec::new();
|
||||
for candidate in TOOL_CANDIDATES {
|
||||
let detection_names = if candidate.detection_names.is_empty() {
|
||||
&[candidate.label][..]
|
||||
} else {
|
||||
candidate.detection_names
|
||||
};
|
||||
|
||||
if detection_names
|
||||
.iter()
|
||||
.any(|name| which(name).is_ok())
|
||||
{
|
||||
available.push(candidate.label.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if available.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(available)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::shell::BashShell;
|
||||
@@ -182,12 +351,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serialize_workspace_write_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
let mut context = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo", "/tmp"], false)),
|
||||
None,
|
||||
);
|
||||
context.operating_system = None;
|
||||
context.common_tools = None;
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
<cwd>/repo</cwd>
|
||||
@@ -205,12 +376,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serialize_read_only_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
let mut context = EnvironmentContext::new(
|
||||
None,
|
||||
Some(AskForApproval::Never),
|
||||
Some(SandboxPolicy::ReadOnly),
|
||||
None,
|
||||
);
|
||||
context.operating_system = None;
|
||||
context.common_tools = None;
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
<approval_policy>never</approval_policy>
|
||||
@@ -223,12 +396,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serialize_full_access_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
let mut context = EnvironmentContext::new(
|
||||
None,
|
||||
Some(AskForApproval::OnFailure),
|
||||
Some(SandboxPolicy::DangerFullAccess),
|
||||
None,
|
||||
);
|
||||
context.operating_system = None;
|
||||
context.common_tools = None;
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
<approval_policy>on-failure</approval_policy>
|
||||
@@ -239,6 +414,31 @@ mod tests {
|
||||
assert_eq!(context.serialize_to_xml(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_environment_context_includes_os_and_tools() {
|
||||
let mut context = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(workspace_write_policy(vec!["/repo"], true)),
|
||||
None,
|
||||
);
|
||||
context.operating_system = Some(OperatingSystemInfo {
|
||||
family: Some("macos".to_string()),
|
||||
version: Some("14.0".to_string()),
|
||||
architecture: Some("aarch64".to_string()),
|
||||
});
|
||||
context.common_tools = Some(vec!["rg".to_string(), "git".to_string()]);
|
||||
|
||||
let xml = context.serialize_to_xml();
|
||||
assert!(xml.contains("<operating_system>"));
|
||||
assert!(xml.contains("<family>macos</family>"));
|
||||
assert!(xml.contains("<version>14.0</version>"));
|
||||
assert!(xml.contains("<architecture>aarch64</architecture>"));
|
||||
assert!(xml.contains("<common_tools>"));
|
||||
assert!(xml.contains("<tool>rg</tool>"));
|
||||
assert!(xml.contains("<tool>git</tool>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equals_except_shell_compares_approval_policy() {
|
||||
// Approval policy
|
||||
|
||||
@@ -113,3 +113,5 @@ pub use codex_protocol::models::LocalShellExecAction;
|
||||
pub use codex_protocol::models::LocalShellStatus;
|
||||
pub use codex_protocol::models::ReasoningItemContent;
|
||||
pub use codex_protocol::models::ResponseItem;
|
||||
pub use environment_context::ToolCandidate;
|
||||
pub use environment_context::TOOL_CANDIDATES;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use codex_core::environment_context::TOOL_CANDIDATES;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ConversationManager;
|
||||
use codex_core::ModelProviderInfo;
|
||||
@@ -17,12 +18,15 @@ use codex_core::shell::default_user_shell;
|
||||
use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::load_sse_fixture_with_id;
|
||||
use core_test_support::wait_for_event;
|
||||
use os_info::Type as OsType;
|
||||
use os_info::Version;
|
||||
use tempfile::TempDir;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
use which::which;
|
||||
|
||||
fn text_user_input(text: String) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
@@ -32,19 +36,127 @@ fn text_user_input(text: String) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
fn default_env_context_str(cwd: &str, shell: &Shell) -> String {
|
||||
format!(
|
||||
r#"<environment_context>
|
||||
<cwd>{}</cwd>
|
||||
<approval_policy>on-request</approval_policy>
|
||||
<sandbox_mode>read-only</sandbox_mode>
|
||||
<network_access>restricted</network_access>
|
||||
{}</environment_context>"#,
|
||||
cwd,
|
||||
match shell.name() {
|
||||
Some(name) => format!(" <shell>{name}</shell>\n"),
|
||||
None => String::new(),
|
||||
fn render_env_context(
|
||||
cwd: Option<String>,
|
||||
approval_policy: Option<&str>,
|
||||
sandbox_mode: Option<&str>,
|
||||
network_access: Option<&str>,
|
||||
writable_roots: Vec<String>,
|
||||
shell_name: Option<String>,
|
||||
) -> String {
|
||||
let mut lines = vec!["<environment_context>".to_string()];
|
||||
if let Some(cwd) = cwd {
|
||||
lines.push(format!(" <cwd>{cwd}</cwd>"));
|
||||
}
|
||||
if let Some(approval_policy) = approval_policy {
|
||||
lines.push(format!(" <approval_policy>{approval_policy}</approval_policy>"));
|
||||
}
|
||||
if let Some(sandbox_mode) = sandbox_mode {
|
||||
lines.push(format!(" <sandbox_mode>{sandbox_mode}</sandbox_mode>"));
|
||||
}
|
||||
if let Some(network_access) = network_access {
|
||||
lines.push(format!(" <network_access>{network_access}</network_access>"));
|
||||
}
|
||||
if !writable_roots.is_empty() {
|
||||
lines.push(" <writable_roots>".to_string());
|
||||
for root in writable_roots {
|
||||
lines.push(format!(" <root>{root}</root>"));
|
||||
}
|
||||
lines.push(" </writable_roots>".to_string());
|
||||
}
|
||||
if let Some(os_block) = operating_system_block() {
|
||||
lines.push(os_block);
|
||||
}
|
||||
if let Some(tools_block) = common_tools_block() {
|
||||
lines.push(tools_block);
|
||||
}
|
||||
if let Some(shell_name) = shell_name {
|
||||
lines.push(format!(" <shell>{shell_name}</shell>"));
|
||||
}
|
||||
lines.push("</environment_context>".to_string());
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn operating_system_block() -> Option<String> {
|
||||
let info = os_info::get();
|
||||
let family = match info.os_type() {
|
||||
OsType::Unknown => None,
|
||||
other => Some(other.to_string()),
|
||||
};
|
||||
let version = match info.version() {
|
||||
Version::Unknown => None,
|
||||
other => {
|
||||
let text = other.to_string();
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(text)
|
||||
}
|
||||
}
|
||||
};
|
||||
let architecture = {
|
||||
let arch = std::env::consts::ARCH;
|
||||
if arch.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(arch.to_string())
|
||||
}
|
||||
};
|
||||
|
||||
if family.is_none() && version.is_none() && architecture.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut lines = vec![" <operating_system>".to_string()];
|
||||
if let Some(family) = family {
|
||||
lines.push(format!(" <family>{family}</family>"));
|
||||
}
|
||||
if let Some(version) = version {
|
||||
lines.push(format!(" <version>{version}</version>"));
|
||||
}
|
||||
if let Some(architecture) = architecture {
|
||||
lines.push(format!(" <architecture>{architecture}</architecture>"));
|
||||
}
|
||||
lines.push(" </operating_system>".to_string());
|
||||
Some(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn common_tools_block() -> Option<String> {
|
||||
let mut available = Vec::new();
|
||||
for candidate in TOOL_CANDIDATES {
|
||||
let detection_names = if candidate.detection_names.is_empty() {
|
||||
&[candidate.label][..]
|
||||
} else {
|
||||
candidate.detection_names
|
||||
};
|
||||
if detection_names
|
||||
.iter()
|
||||
.any(|name| which(name).is_ok())
|
||||
{
|
||||
available.push(candidate.label);
|
||||
}
|
||||
}
|
||||
if available.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut lines = vec![" <common_tools>".to_string()];
|
||||
for tool in available {
|
||||
lines.push(format!(" <tool>{tool}</tool>"));
|
||||
}
|
||||
lines.push(" </common_tools>".to_string());
|
||||
Some(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn default_env_context_str(cwd: &str, shell: &Shell) -> String {
|
||||
let shell_name = shell.name();
|
||||
render_env_context(
|
||||
Some(cwd.to_string()),
|
||||
Some("on-request"),
|
||||
Some("read-only"),
|
||||
Some("restricted"),
|
||||
Vec::new(),
|
||||
shell_name,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -296,18 +408,13 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
|
||||
|
||||
let shell = default_user_shell().await;
|
||||
|
||||
let expected_env_text = format!(
|
||||
r#"<environment_context>
|
||||
<cwd>{}</cwd>
|
||||
<approval_policy>on-request</approval_policy>
|
||||
<sandbox_mode>read-only</sandbox_mode>
|
||||
<network_access>restricted</network_access>
|
||||
{}</environment_context>"#,
|
||||
cwd.path().to_string_lossy(),
|
||||
match shell.name() {
|
||||
Some(name) => format!(" <shell>{name}</shell>\n"),
|
||||
None => String::new(),
|
||||
}
|
||||
let expected_env_text = render_env_context(
|
||||
Some(cwd.path().to_string_lossy().to_string()),
|
||||
Some("on-request"),
|
||||
Some("read-only"),
|
||||
Some("restricted"),
|
||||
Vec::new(),
|
||||
shell.name(),
|
||||
);
|
||||
let expected_ui_text =
|
||||
"<user_instructions>\n\nbe consistent and helpful\n\n</user_instructions>";
|
||||
@@ -456,16 +563,13 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() {
|
||||
// After overriding the turn context, the environment context should be emitted again
|
||||
// reflecting the new approval policy and sandbox settings. Omit cwd because it did
|
||||
// not change.
|
||||
let expected_env_text_2 = format!(
|
||||
r#"<environment_context>
|
||||
<approval_policy>never</approval_policy>
|
||||
<sandbox_mode>workspace-write</sandbox_mode>
|
||||
<network_access>enabled</network_access>
|
||||
<writable_roots>
|
||||
<root>{}</root>
|
||||
</writable_roots>
|
||||
</environment_context>"#,
|
||||
writable.path().to_string_lossy()
|
||||
let expected_env_text_2 = render_env_context(
|
||||
None,
|
||||
Some("never"),
|
||||
Some("workspace-write"),
|
||||
Some("enabled"),
|
||||
vec![writable.path().to_string_lossy().to_string()],
|
||||
None,
|
||||
);
|
||||
let expected_env_msg_2 = serde_json::json!({
|
||||
"type": "message",
|
||||
@@ -578,18 +682,13 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() {
|
||||
"role": "user",
|
||||
"content": [ { "type": "input_text", "text": "hello 2" } ]
|
||||
});
|
||||
let expected_env_text_2 = format!(
|
||||
r#"<environment_context>
|
||||
<cwd>{}</cwd>
|
||||
<approval_policy>never</approval_policy>
|
||||
<sandbox_mode>workspace-write</sandbox_mode>
|
||||
<network_access>enabled</network_access>
|
||||
<writable_roots>
|
||||
<root>{}</root>
|
||||
</writable_roots>
|
||||
</environment_context>"#,
|
||||
new_cwd.path().to_string_lossy(),
|
||||
writable.path().to_string_lossy(),
|
||||
let expected_env_text_2 = render_env_context(
|
||||
Some(new_cwd.path().to_string_lossy().to_string()),
|
||||
Some("never"),
|
||||
Some("workspace-write"),
|
||||
Some("enabled"),
|
||||
vec![writable.path().to_string_lossy().to_string()],
|
||||
None,
|
||||
);
|
||||
let expected_env_msg_2 = serde_json::json!({
|
||||
"type": "message",
|
||||
@@ -818,14 +917,13 @@ async fn send_user_turn_with_changes_sends_environment_context() {
|
||||
]);
|
||||
assert_eq!(body1["input"], expected_input_1);
|
||||
|
||||
let expected_env_msg_2 = text_user_input(format!(
|
||||
r#"<environment_context>
|
||||
<cwd>{}</cwd>
|
||||
<approval_policy>never</approval_policy>
|
||||
<sandbox_mode>danger-full-access</sandbox_mode>
|
||||
<network_access>enabled</network_access>
|
||||
</environment_context>"#,
|
||||
default_cwd.to_string_lossy()
|
||||
let expected_env_msg_2 = text_user_input(render_env_context(
|
||||
Some(default_cwd.to_string_lossy().to_string()),
|
||||
Some("never"),
|
||||
Some("danger-full-access"),
|
||||
Some("enabled"),
|
||||
Vec::new(),
|
||||
shell.name(),
|
||||
));
|
||||
let expected_user_message_2 = text_user_input("hello 2".to_string());
|
||||
let expected_input_2 = serde_json::Value::Array(vec![
|
||||
|
||||
@@ -4474,14 +4474,33 @@ impl ChatWidget<'_> {
|
||||
Ok(content) => (true, content),
|
||||
Err(error) => (false, error),
|
||||
};
|
||||
let entry_idx = self
|
||||
.tools_state
|
||||
.running_custom_tools
|
||||
.remove(&ToolCallId(call_id.clone()));
|
||||
|
||||
if tool_name == "apply_patch" && success {
|
||||
if let Some(idx) = entry_idx {
|
||||
if idx < self.history_cells.len() {
|
||||
let is_running_tool = self.history_cells[idx]
|
||||
.as_any()
|
||||
.downcast_ref::<history_cell::RunningToolCallCell>()
|
||||
.is_some();
|
||||
if is_running_tool {
|
||||
self.history_remove_at(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.bottom_pane
|
||||
.update_status_text("responding".to_string());
|
||||
self.maybe_hide_spinner();
|
||||
return;
|
||||
}
|
||||
|
||||
if tool_name == "wait" && success {
|
||||
let target = wait_target_from_params(params_string.as_ref(), &call_id);
|
||||
let message = format!("Waited for {}", target);
|
||||
if let Some(idx) = self
|
||||
.tools_state
|
||||
.running_custom_tools
|
||||
.remove(&ToolCallId(call_id.clone()))
|
||||
{
|
||||
if let Some(idx) = entry_idx {
|
||||
if idx < self.history_cells.len() {
|
||||
self.history_replace_at(
|
||||
idx,
|
||||
@@ -4516,11 +4535,7 @@ impl ChatWidget<'_> {
|
||||
kind: HistoryCellType::Error,
|
||||
};
|
||||
|
||||
if let Some(idx) = self
|
||||
.tools_state
|
||||
.running_custom_tools
|
||||
.remove(&ToolCallId(call_id.clone()))
|
||||
{
|
||||
if let Some(idx) = entry_idx {
|
||||
if idx < self.history_cells.len() {
|
||||
self.history_replace_at(idx, Box::new(wait_cancelled_cell));
|
||||
} else {
|
||||
@@ -4550,11 +4565,7 @@ impl ChatWidget<'_> {
|
||||
success,
|
||||
content,
|
||||
);
|
||||
if let Some(idx) = self
|
||||
.tools_state
|
||||
.running_custom_tools
|
||||
.remove(&ToolCallId(call_id))
|
||||
{
|
||||
if let Some(idx) = entry_idx {
|
||||
if idx < self.history_cells.len() {
|
||||
self.history_replace_at(idx, Box::new(completed));
|
||||
} else {
|
||||
@@ -4577,11 +4588,7 @@ impl ChatWidget<'_> {
|
||||
success,
|
||||
content,
|
||||
);
|
||||
if let Some(idx) = self
|
||||
.tools_state
|
||||
.running_custom_tools
|
||||
.remove(&ToolCallId(call_id))
|
||||
{
|
||||
if let Some(idx) = entry_idx {
|
||||
if idx < self.history_cells.len() {
|
||||
self.history_replace_at(idx, Box::new(completed));
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user