mirror of
https://github.com/openai/codex.git
synced 2026-05-21 19:45:26 +00:00
Compare commits
5 Commits
pr23756
...
pakrym/asy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f117ee8f7 | ||
|
|
9d8c893cd3 | ||
|
|
109a876270 | ||
|
|
bc1714b51e | ||
|
|
1390d7be39 |
@@ -821,6 +821,7 @@ mod tests {
|
||||
match ConfigBuilder::default().build().await {
|
||||
Ok(config) => config,
|
||||
Err(_) => Config::load_default_with_cli_overrides(Vec::new())
|
||||
.await
|
||||
.expect("default config should load"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ use codex_app_server_protocol::FsWriteFileResponse;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_exec_server::CopyOptions;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
@@ -34,7 +34,7 @@ pub(crate) struct FsApi {
|
||||
impl Default for FsApi {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file_system: Environment::default().get_filesystem(),
|
||||
file_system: Arc::new(LOCAL_FS),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,6 +698,7 @@ mod tests {
|
||||
match ConfigBuilder::default().build().await {
|
||||
Ok(config) => config,
|
||||
Err(_) => Config::load_default_with_cli_overrides(Vec::new())
|
||||
.await
|
||||
.expect("default config should load"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,12 +447,14 @@ pub async fn run_main_with_transport(
|
||||
Err(err) => {
|
||||
let message = config_warning_from_error("Invalid configuration; using defaults.", &err);
|
||||
config_warnings.push(message);
|
||||
Config::load_default_with_cli_overrides(cli_kv_overrides.clone()).map_err(|e| {
|
||||
std::io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("error loading default config after config error: {e}"),
|
||||
)
|
||||
})?
|
||||
Config::load_default_with_cli_overrides(cli_kv_overrides.clone())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
std::io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("error loading default config after config error: {e}"),
|
||||
)
|
||||
})?
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ async fn apply_role_to_config_inner(
|
||||
role_layer_toml,
|
||||
preserve_current_profile,
|
||||
preserve_current_provider,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ fn preservation_policy(config: &Config, role_layer_toml: &TomlValue) -> (bool, b
|
||||
mod reload {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn build_next_config(
|
||||
pub(super) async fn build_next_config(
|
||||
config: &Config,
|
||||
role_layer_toml: TomlValue,
|
||||
preserve_current_profile: bool,
|
||||
@@ -164,7 +165,8 @@ mod reload {
|
||||
reload_overrides(config, preserve_current_provider),
|
||||
config.codex_home.clone(),
|
||||
config_layer_stack,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
if preserve_current_profile {
|
||||
next_config.active_profile = config.active_profile.clone();
|
||||
}
|
||||
|
||||
@@ -550,7 +550,7 @@ async fn get_base_instructions_no_user_content() {
|
||||
];
|
||||
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let config = test_config();
|
||||
let config = test_config().await;
|
||||
|
||||
for test_case in test_cases {
|
||||
let model_info = model_info_for_slug(test_case.slug, &config);
|
||||
@@ -715,8 +715,8 @@ fn collect_explicit_app_ids_from_skill_items_skips_plain_mentions_with_skill_con
|
||||
assert_eq!(connector_ids, HashSet::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_app_mcp_tools_remain_visible_without_search_selection() {
|
||||
#[tokio::test]
|
||||
async fn non_app_mcp_tools_remain_visible_without_search_selection() {
|
||||
let mcp_tools = HashMap::from([
|
||||
(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
@@ -747,7 +747,7 @@ fn non_app_mcp_tools_remain_visible_without_search_selection() {
|
||||
&explicitly_enabled_connectors,
|
||||
&HashMap::new(),
|
||||
);
|
||||
let config = test_config();
|
||||
let config = test_config().await;
|
||||
selected_mcp_tools.extend(filter_codex_apps_mcp_tools(
|
||||
&mcp_tools,
|
||||
&connectors,
|
||||
@@ -759,8 +759,8 @@ fn non_app_mcp_tools_remain_visible_without_search_selection() {
|
||||
assert_eq!(tool_names, vec!["mcp__rmcp__echo".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_selection_keeps_codex_apps_tools_without_mentions() {
|
||||
#[tokio::test]
|
||||
async fn search_tool_selection_keeps_codex_apps_tools_without_mentions() {
|
||||
let selected_tool_names = [
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
"mcp__rmcp__echo".to_string(),
|
||||
@@ -794,7 +794,7 @@ fn search_tool_selection_keeps_codex_apps_tools_without_mentions() {
|
||||
&explicitly_enabled_connectors,
|
||||
&HashMap::new(),
|
||||
);
|
||||
let config = test_config();
|
||||
let config = test_config().await;
|
||||
selected_mcp_tools.extend(filter_codex_apps_mcp_tools(
|
||||
&mcp_tools,
|
||||
&connectors,
|
||||
@@ -812,8 +812,8 @@ fn search_tool_selection_keeps_codex_apps_tools_without_mentions() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apps_mentions_add_codex_apps_tools_to_search_selected_set() {
|
||||
#[tokio::test]
|
||||
async fn apps_mentions_add_codex_apps_tools_to_search_selected_set() {
|
||||
let selected_tool_names = ["mcp__rmcp__echo".to_string()];
|
||||
let mcp_tools = HashMap::from([
|
||||
(
|
||||
@@ -844,7 +844,7 @@ fn apps_mentions_add_codex_apps_tools_to_search_selected_set() {
|
||||
&explicitly_enabled_connectors,
|
||||
&HashMap::new(),
|
||||
);
|
||||
let config = test_config();
|
||||
let config = test_config().await;
|
||||
selected_mcp_tools.extend(filter_codex_apps_mcp_tools(
|
||||
&mcp_tools,
|
||||
&connectors,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -209,13 +209,14 @@ fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option<PathBuf> {
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(crate) fn test_config() -> Config {
|
||||
pub(crate) async fn test_config() -> Config {
|
||||
let codex_home = tempfile::tempdir().expect("create temp dir");
|
||||
Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides::default(),
|
||||
codex_home.path().to_path_buf(),
|
||||
)
|
||||
.await
|
||||
.expect("load default test config")
|
||||
}
|
||||
|
||||
@@ -722,6 +723,7 @@ impl ConfigBuilder {
|
||||
codex_home,
|
||||
config_layer_stack,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,7 +739,7 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Load a default configuration when user config files are invalid.
|
||||
pub fn load_default_with_cli_overrides(
|
||||
pub async fn load_default_with_cli_overrides(
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
) -> std::io::Result<Self> {
|
||||
let codex_home = find_codex_home()?;
|
||||
@@ -756,6 +758,7 @@ impl Config {
|
||||
codex_home,
|
||||
ConfigLayerStack::default(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// This is a secondary way of creating [Config], which is appropriate when
|
||||
@@ -1660,7 +1663,7 @@ pub struct GhostSnapshotToml {
|
||||
|
||||
impl ConfigToml {
|
||||
/// Derive the effective sandbox policy from the configuration.
|
||||
fn derive_sandbox_policy(
|
||||
async fn derive_sandbox_policy(
|
||||
&self,
|
||||
sandbox_mode_override: Option<SandboxMode>,
|
||||
profile_sandbox_mode: Option<SandboxMode>,
|
||||
@@ -1671,15 +1674,13 @@ impl ConfigToml {
|
||||
let sandbox_mode_was_explicit = sandbox_mode_override.is_some()
|
||||
|| profile_sandbox_mode.is_some()
|
||||
|| self.sandbox_mode.is_some();
|
||||
let resolved_sandbox_mode = sandbox_mode_override
|
||||
.or(profile_sandbox_mode)
|
||||
.or(self.sandbox_mode)
|
||||
.or_else(|| {
|
||||
// If no sandbox_mode is set but this directory has a trust decision,
|
||||
// default to workspace-write except on unsandboxed Windows where we
|
||||
// default to read-only.
|
||||
self.get_active_project(resolved_cwd).and_then(|p| {
|
||||
if p.is_trusted() || p.is_untrusted() {
|
||||
let trust_default_sandbox_mode = if sandbox_mode_was_explicit {
|
||||
None
|
||||
} else {
|
||||
self.get_active_project(resolved_cwd)
|
||||
.await
|
||||
.and_then(|project| {
|
||||
if project.is_trusted() || project.is_untrusted() {
|
||||
if cfg!(target_os = "windows")
|
||||
&& windows_sandbox_level
|
||||
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
|
||||
@@ -1692,7 +1693,11 @@ impl ConfigToml {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
let resolved_sandbox_mode = sandbox_mode_override
|
||||
.or(profile_sandbox_mode)
|
||||
.or(self.sandbox_mode)
|
||||
.or(trust_default_sandbox_mode)
|
||||
.unwrap_or_default();
|
||||
let mut sandbox_policy = match resolved_sandbox_mode {
|
||||
SandboxMode::ReadOnly => SandboxPolicy::new_read_only_policy(),
|
||||
@@ -1742,7 +1747,7 @@ impl ConfigToml {
|
||||
|
||||
/// Resolves the cwd to an existing project, or returns None if ConfigToml
|
||||
/// does not contain a project corresponding to cwd or a git repo for cwd
|
||||
pub fn get_active_project(&self, resolved_cwd: &Path) -> Option<ProjectConfig> {
|
||||
pub async fn get_active_project(&self, resolved_cwd: &Path) -> Option<ProjectConfig> {
|
||||
let projects = self.projects.clone().unwrap_or_default();
|
||||
|
||||
if let Some(project_config) = projects.get(&resolved_cwd.to_string_lossy().to_string()) {
|
||||
@@ -1752,7 +1757,7 @@ impl ConfigToml {
|
||||
// If cwd lives inside a git repo/worktree, check whether the root git project
|
||||
// (the primary repository working directory) is trusted. This lets
|
||||
// worktrees inherit trust from the main project.
|
||||
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd)
|
||||
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd).await
|
||||
&& let Some(project_config_for_root) =
|
||||
projects.get(&repo_root.to_string_lossy().to_string_lossy().to_string())
|
||||
{
|
||||
@@ -1999,17 +2004,17 @@ pub(crate) fn resolve_web_search_mode_for_turn(
|
||||
|
||||
impl Config {
|
||||
#[cfg(test)]
|
||||
fn load_from_base_config_with_overrides(
|
||||
async fn load_from_base_config_with_overrides(
|
||||
cfg: ConfigToml,
|
||||
overrides: ConfigOverrides,
|
||||
codex_home: PathBuf,
|
||||
) -> std::io::Result<Self> {
|
||||
// Note this ignores requirements.toml enforcement for tests.
|
||||
let config_layer_stack = ConfigLayerStack::default();
|
||||
Self::load_config_with_layer_stack(cfg, overrides, codex_home, config_layer_stack)
|
||||
Self::load_config_with_layer_stack(cfg, overrides, codex_home, config_layer_stack).await
|
||||
}
|
||||
|
||||
pub(crate) fn load_config_with_layer_stack(
|
||||
pub(crate) async fn load_config_with_layer_stack(
|
||||
cfg: ConfigToml,
|
||||
overrides: ConfigOverrides,
|
||||
codex_home: PathBuf,
|
||||
@@ -2128,6 +2133,7 @@ impl Config {
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let active_project = cfg
|
||||
.get_active_project(&resolved_cwd)
|
||||
.await
|
||||
.unwrap_or(ProjectConfig { trust_level: None });
|
||||
let permission_config_syntax = resolve_permission_config_syntax(
|
||||
&config_layer_stack,
|
||||
@@ -2215,13 +2221,15 @@ impl Config {
|
||||
)
|
||||
} else {
|
||||
let configured_network_proxy_config = NetworkProxyConfig::default();
|
||||
let mut sandbox_policy = cfg.derive_sandbox_policy(
|
||||
sandbox_mode,
|
||||
config_profile.sandbox_mode,
|
||||
windows_sandbox_level,
|
||||
&resolved_cwd,
|
||||
Some(&constrained_sandbox_policy),
|
||||
);
|
||||
let mut sandbox_policy = cfg
|
||||
.derive_sandbox_policy(
|
||||
sandbox_mode,
|
||||
config_profile.sandbox_mode,
|
||||
windows_sandbox_level,
|
||||
&resolved_cwd,
|
||||
Some(&constrained_sandbox_policy),
|
||||
)
|
||||
.await;
|
||||
if let SandboxPolicy::WorkspaceWrite { writable_roots, .. } = &mut sandbox_policy {
|
||||
for path in &additional_writable_roots {
|
||||
if !writable_roots.iter().any(|existing| existing == path) {
|
||||
|
||||
@@ -14,8 +14,8 @@ fn normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths() {
|
||||
assert_eq!(parsed, PathBuf::from(r"D:\c\x\worktrees\2508\swift-base"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restricted_read_implicitly_allows_helper_executables() -> std::io::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn restricted_read_implicitly_allows_helper_executables() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let cwd = temp_dir.path().join("workspace");
|
||||
let codex_home = temp_dir.path().join(".codex");
|
||||
@@ -54,7 +54,8 @@ fn restricted_read_implicitly_allows_helper_executables() -> std::io::Result<()>
|
||||
..Default::default()
|
||||
},
|
||||
codex_home,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
let expected_zsh = AbsolutePathBuf::try_from(zsh_path)?;
|
||||
let expected_allowed_arg0_dir = AbsolutePathBuf::try_from(allowed_arg0_dir)?;
|
||||
|
||||
@@ -686,7 +686,7 @@ async fn project_trust_context(
|
||||
let projects = project_trust_config.projects.unwrap_or_default();
|
||||
|
||||
let project_root_key = project_root.as_path().to_string_lossy().to_string();
|
||||
let repo_root = resolve_root_git_project_for_trust(cwd.as_path());
|
||||
let repo_root = resolve_root_git_project_for_trust(cwd.as_path()).await;
|
||||
let repo_root_key = repo_root
|
||||
.as_ref()
|
||||
.map(|root| root.to_string_lossy().to_string());
|
||||
|
||||
@@ -426,10 +426,14 @@ async fn test_get_git_working_tree_state_branch_fallback() {
|
||||
assert_eq!(state.sha, GitSha::new(&remote_sha));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_root_git_project_for_trust_returns_none_outside_repo() {
|
||||
#[tokio::test]
|
||||
async fn resolve_root_git_project_for_trust_returns_none_outside_repo() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
assert!(resolve_root_git_project_for_trust(tmp.path()).is_none());
|
||||
assert!(
|
||||
resolve_root_git_project_for_trust(tmp.path())
|
||||
.await
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -439,12 +443,15 @@ async fn resolve_root_git_project_for_trust_regular_repo_returns_repo_root() {
|
||||
let expected = std::fs::canonicalize(&repo_path).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
resolve_root_git_project_for_trust(&repo_path),
|
||||
resolve_root_git_project_for_trust(&repo_path).await,
|
||||
Some(expected.clone())
|
||||
);
|
||||
let nested = repo_path.join("sub/dir");
|
||||
std::fs::create_dir_all(&nested).unwrap();
|
||||
assert_eq!(resolve_root_git_project_for_trust(&nested), Some(expected));
|
||||
assert_eq!(
|
||||
resolve_root_git_project_for_trust(&nested).await,
|
||||
Some(expected)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -467,18 +474,20 @@ async fn resolve_root_git_project_for_trust_detects_worktree_and_returns_main_ro
|
||||
.expect("git worktree add");
|
||||
|
||||
let expected = std::fs::canonicalize(&repo_path).ok();
|
||||
let got =
|
||||
resolve_root_git_project_for_trust(&wt_root).and_then(|p| std::fs::canonicalize(p).ok());
|
||||
let got = resolve_root_git_project_for_trust(&wt_root)
|
||||
.await
|
||||
.and_then(|path| std::fs::canonicalize(path).ok());
|
||||
assert_eq!(got, expected);
|
||||
let nested = wt_root.join("nested/sub");
|
||||
std::fs::create_dir_all(&nested).unwrap();
|
||||
let got_nested =
|
||||
resolve_root_git_project_for_trust(&nested).and_then(|p| std::fs::canonicalize(p).ok());
|
||||
let got_nested = resolve_root_git_project_for_trust(&nested)
|
||||
.await
|
||||
.and_then(|path| std::fs::canonicalize(path).ok());
|
||||
assert_eq!(got_nested, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_root_git_project_for_trust_detects_worktree_pointer_without_git_command() {
|
||||
#[tokio::test]
|
||||
async fn resolve_root_git_project_for_trust_detects_worktree_pointer_without_git_command() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
let repo_root = tmp.path().join("repo");
|
||||
let common_dir = repo_root.join(".git");
|
||||
@@ -495,17 +504,17 @@ fn resolve_root_git_project_for_trust_detects_worktree_pointer_without_git_comma
|
||||
|
||||
let expected = std::fs::canonicalize(&repo_root).unwrap();
|
||||
assert_eq!(
|
||||
resolve_root_git_project_for_trust(&worktree_root),
|
||||
resolve_root_git_project_for_trust(&worktree_root).await,
|
||||
Some(expected.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_root_git_project_for_trust(&worktree_root.join("nested")),
|
||||
resolve_root_git_project_for_trust(&worktree_root.join("nested")).await,
|
||||
Some(expected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_root_git_project_for_trust_non_worktrees_gitdir_returns_none() {
|
||||
#[tokio::test]
|
||||
async fn resolve_root_git_project_for_trust_non_worktrees_gitdir_returns_none() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
let proj = tmp.path().join("proj");
|
||||
std::fs::create_dir_all(proj.join("nested")).unwrap();
|
||||
@@ -520,8 +529,12 @@ fn resolve_root_git_project_for_trust_non_worktrees_gitdir_returns_none() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(resolve_root_git_project_for_trust(&proj).is_none());
|
||||
assert!(resolve_root_git_project_for_trust(&proj.join("nested")).is_none());
|
||||
assert!(resolve_root_git_project_for_trust(&proj).await.is_none());
|
||||
assert!(
|
||||
resolve_root_git_project_for_trust(&proj.join("nested"))
|
||||
.await
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -743,9 +743,9 @@ async fn interrupt_and_drain_turn(codex: &Codex) -> anyhow::Result<()> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_change_invalidates_cached_session() {
|
||||
let parent_config = crate::config::test_config();
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_change_invalidates_cached_session() {
|
||||
let parent_config = crate::config::test_config().await;
|
||||
let cached_spawn_config =
|
||||
build_guardian_review_session_config(&parent_config, None, "active-model", None)
|
||||
.expect("cached guardian config");
|
||||
|
||||
@@ -963,9 +963,9 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
let mut parent_config = test_config();
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
let mut parent_config = test_config().await;
|
||||
let network = NetworkProxySpec::from_config_and_constraints(
|
||||
NetworkProxyConfig::default(),
|
||||
Some(NetworkConstraints {
|
||||
@@ -1005,9 +1005,9 @@ fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_overrides_parent_developer_instructions() {
|
||||
let mut parent_config = test_config();
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_overrides_parent_developer_instructions() {
|
||||
let mut parent_config = test_config().await;
|
||||
parent_config.developer_instructions =
|
||||
Some("parent or managed config should not replace guardian policy".to_string());
|
||||
|
||||
@@ -1021,9 +1021,9 @@ fn guardian_review_session_config_overrides_parent_developer_instructions() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
let mut parent_config = test_config();
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
let mut parent_config = test_config().await;
|
||||
let mut parent_network = NetworkProxyConfig::default();
|
||||
parent_network.network.enabled = true;
|
||||
parent_network.network.allowed_domains = vec!["parent.example".to_string()];
|
||||
@@ -1061,9 +1061,9 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_rejects_pinned_collab_feature() {
|
||||
let mut parent_config = test_config();
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_rejects_pinned_collab_feature() {
|
||||
let mut parent_config = test_config().await;
|
||||
parent_config.features = ManagedFeatures::from_configured(
|
||||
parent_config.features.get().clone(),
|
||||
Some(Sourced {
|
||||
@@ -1085,9 +1085,9 @@ fn guardian_review_session_config_rejects_pinned_collab_feature() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_slug() {
|
||||
let mut parent_config = test_config();
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_slug() {
|
||||
let mut parent_config = test_config().await;
|
||||
parent_config.model = Some("configured-model".to_string());
|
||||
|
||||
let guardian_config =
|
||||
@@ -1097,8 +1097,8 @@ fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_
|
||||
assert_eq!(guardian_config.model, Some("active-model".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_uses_requirements_guardian_override() {
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_uses_requirements_guardian_override() {
|
||||
let codex_home = tempfile::tempdir().expect("create temp dir");
|
||||
let workspace = tempfile::tempdir().expect("create temp dir");
|
||||
let config_layer_stack = ConfigLayerStack::new(
|
||||
@@ -1121,6 +1121,7 @@ fn guardian_review_session_config_uses_requirements_guardian_override() {
|
||||
codex_home.path().to_path_buf(),
|
||||
config_layer_stack,
|
||||
)
|
||||
.await
|
||||
.expect("load config");
|
||||
|
||||
let guardian_config =
|
||||
@@ -1133,8 +1134,9 @@ fn guardian_review_session_config_uses_requirements_guardian_override() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guardian_review_session_config_uses_default_guardian_policy_without_requirements_override() {
|
||||
#[tokio::test]
|
||||
async fn guardian_review_session_config_uses_default_guardian_policy_without_requirements_override()
|
||||
{
|
||||
let codex_home = tempfile::tempdir().expect("create temp dir");
|
||||
let workspace = tempfile::tempdir().expect("create temp dir");
|
||||
let config_layer_stack =
|
||||
@@ -1149,6 +1151,7 @@ fn guardian_review_session_config_uses_default_guardian_policy_without_requireme
|
||||
codex_home.path().to_path_buf(),
|
||||
config_layer_stack,
|
||||
)
|
||||
.await
|
||||
.expect("load config");
|
||||
|
||||
let guardian_config =
|
||||
|
||||
@@ -143,9 +143,9 @@ fn codex_apps_mcp_url_for_base_url_keeps_existing_paths() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_mcp_url_uses_legacy_codex_apps_path() {
|
||||
let mut config = crate::config::test_config();
|
||||
#[tokio::test]
|
||||
async fn codex_apps_mcp_url_uses_legacy_codex_apps_path() {
|
||||
let mut config = crate::config::test_config().await;
|
||||
config.chatgpt_base_url = "https://chatgpt.com".to_string();
|
||||
|
||||
assert_eq!(
|
||||
@@ -154,9 +154,9 @@ fn codex_apps_mcp_url_uses_legacy_codex_apps_path() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_server_config_uses_legacy_codex_apps_path() {
|
||||
let mut config = crate::config::test_config();
|
||||
#[tokio::test]
|
||||
async fn codex_apps_server_config_uses_legacy_codex_apps_path() {
|
||||
let mut config = crate::config::test_config().await;
|
||||
config.chatgpt_base_url = "https://chatgpt.com".to_string();
|
||||
|
||||
let mut servers = with_codex_apps_mcp(HashMap::new(), false, None, &config);
|
||||
|
||||
@@ -467,7 +467,7 @@ mod phase2 {
|
||||
impl DispatchHarness {
|
||||
async fn new() -> Self {
|
||||
let codex_home = tempfile::tempdir().expect("create temp codex home");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = codex_home.path().to_path_buf();
|
||||
config.cwd = config.codex_home.clone();
|
||||
let config = Arc::new(config);
|
||||
@@ -880,7 +880,7 @@ mod phase2 {
|
||||
#[tokio::test]
|
||||
async fn dispatch_marks_job_for_retry_when_spawn_agent_fails() {
|
||||
let codex_home = tempfile::tempdir().expect("create temp codex home");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = codex_home.path().to_path_buf();
|
||||
config.cwd = config.codex_home.clone();
|
||||
let config = Arc::new(config);
|
||||
|
||||
@@ -2,10 +2,10 @@ use super::*;
|
||||
use crate::config::test_config;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn reasoning_summaries_override_true_enables_support() {
|
||||
#[tokio::test]
|
||||
async fn reasoning_summaries_override_true_enables_support() {
|
||||
let model = model_info_from_slug("unknown-model");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.model_supports_reasoning_summaries = Some(true);
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
@@ -15,11 +15,11 @@ fn reasoning_summaries_override_true_enables_support() {
|
||||
assert_eq!(updated, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
#[tokio::test]
|
||||
async fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
let mut model = model_info_from_slug("unknown-model");
|
||||
model.supports_reasoning_summaries = true;
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.model_supports_reasoning_summaries = Some(false);
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
@@ -27,10 +27,10 @@ fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
assert_eq!(updated, model);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_summaries_override_false_is_noop_when_model_is_false() {
|
||||
#[tokio::test]
|
||||
async fn reasoning_summaries_override_false_is_noop_when_model_is_false() {
|
||||
let model = model_info_from_slug("unknown-model");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.model_supports_reasoning_summaries = Some(false);
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
|
||||
@@ -5,9 +5,9 @@ use crate::models_manager::manager::ModelsManager;
|
||||
use codex_features::Features;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn image_detail_original_feature_enables_explicit_original_without_force() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn image_detail_original_feature_enables_explicit_original_without_force() {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.supports_image_detail_original = true;
|
||||
@@ -25,9 +25,9 @@ fn image_detail_original_feature_enables_explicit_original_without_force() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_original_is_dropped_without_feature_or_model_support() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn explicit_original_is_dropped_without_feature_or_model_support() {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.supports_image_detail_original = true;
|
||||
@@ -47,9 +47,9 @@ fn explicit_original_is_dropped_without_feature_or_model_support() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_non_original_detail_is_dropped() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn unsupported_non_original_detail_is_dropped() {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.supports_image_detail_original = true;
|
||||
|
||||
@@ -57,8 +57,8 @@ pub(crate) async fn build_realtime_startup_context(
|
||||
let history = sess.clone_history().await;
|
||||
let current_thread_section = build_current_thread_section(history.raw_items());
|
||||
let recent_threads = load_recent_threads(sess).await;
|
||||
let recent_work_section = build_recent_work_section(&cwd, &recent_threads);
|
||||
let workspace_section = build_workspace_section_with_user_root(&cwd, home_dir());
|
||||
let recent_work_section = build_recent_work_section(&cwd, &recent_threads).await;
|
||||
let workspace_section = build_workspace_section_with_user_root(&cwd, home_dir()).await;
|
||||
|
||||
if current_thread_section.is_none()
|
||||
&& recent_work_section.is_none()
|
||||
@@ -141,46 +141,57 @@ async fn load_recent_threads(sess: &Session) -> Vec<ThreadMetadata> {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_recent_work_section(cwd: &Path, recent_threads: &[ThreadMetadata]) -> Option<String> {
|
||||
let mut groups: HashMap<PathBuf, Vec<&ThreadMetadata>> = HashMap::new();
|
||||
async fn build_recent_work_section(
|
||||
cwd: &Path,
|
||||
recent_threads: &[ThreadMetadata],
|
||||
) -> Option<String> {
|
||||
let mut groups: HashMap<PathBuf, (bool, Vec<&ThreadMetadata>)> = HashMap::new();
|
||||
for entry in recent_threads {
|
||||
let group =
|
||||
resolve_root_git_project_for_trust(&entry.cwd).unwrap_or_else(|| entry.cwd.clone());
|
||||
groups.entry(group).or_default().push(entry);
|
||||
let git_root = resolve_root_git_project_for_trust(&entry.cwd).await;
|
||||
let is_git_repo = git_root.is_some();
|
||||
let group = git_root.unwrap_or_else(|| entry.cwd.clone());
|
||||
let (group_is_git_repo, entries) = groups
|
||||
.entry(group)
|
||||
.or_insert_with(|| (is_git_repo, Vec::new()));
|
||||
*group_is_git_repo |= is_git_repo;
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
let current_group =
|
||||
resolve_root_git_project_for_trust(cwd).unwrap_or_else(|| cwd.to_path_buf());
|
||||
let current_group = resolve_root_git_project_for_trust(cwd)
|
||||
.await
|
||||
.unwrap_or_else(|| cwd.to_path_buf());
|
||||
let mut groups = groups.into_iter().collect::<Vec<_>>();
|
||||
groups.sort_by(|(left_group, left_entries), (right_group, right_entries)| {
|
||||
let left_latest = left_entries
|
||||
.iter()
|
||||
.map(|entry| entry.updated_at)
|
||||
.max()
|
||||
.unwrap_or_else(Utc::now);
|
||||
let right_latest = right_entries
|
||||
.iter()
|
||||
.map(|entry| entry.updated_at)
|
||||
.max()
|
||||
.unwrap_or_else(Utc::now);
|
||||
(
|
||||
*left_group != current_group,
|
||||
Reverse(left_latest),
|
||||
left_group.as_os_str(),
|
||||
)
|
||||
.cmp(&(
|
||||
*right_group != current_group,
|
||||
Reverse(right_latest),
|
||||
right_group.as_os_str(),
|
||||
))
|
||||
});
|
||||
groups.sort_by(
|
||||
|(left_group, (_, left_entries)), (right_group, (_, right_entries))| {
|
||||
let left_latest = left_entries
|
||||
.iter()
|
||||
.map(|entry| entry.updated_at)
|
||||
.max()
|
||||
.unwrap_or_else(Utc::now);
|
||||
let right_latest = right_entries
|
||||
.iter()
|
||||
.map(|entry| entry.updated_at)
|
||||
.max()
|
||||
.unwrap_or_else(Utc::now);
|
||||
(
|
||||
*left_group != current_group,
|
||||
Reverse(left_latest),
|
||||
left_group.as_os_str(),
|
||||
)
|
||||
.cmp(&(
|
||||
*right_group != current_group,
|
||||
Reverse(right_latest),
|
||||
right_group.as_os_str(),
|
||||
))
|
||||
},
|
||||
);
|
||||
|
||||
let sections = groups
|
||||
.into_iter()
|
||||
.take(MAX_RECENT_WORK_GROUPS)
|
||||
.filter_map(|(group, mut entries)| {
|
||||
.filter_map(|(group, (is_git_repo, mut entries))| {
|
||||
entries.sort_by_key(|entry| Reverse(entry.updated_at));
|
||||
format_thread_group(¤t_group, &group, entries)
|
||||
format_thread_group(¤t_group, &group, is_git_repo, entries)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(!sections.is_empty()).then(|| sections.join("\n\n"))
|
||||
@@ -270,11 +281,11 @@ fn build_current_thread_section(items: &[ResponseItem]) -> Option<String> {
|
||||
Some(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn build_workspace_section_with_user_root(
|
||||
async fn build_workspace_section_with_user_root(
|
||||
cwd: &Path,
|
||||
user_root: Option<PathBuf>,
|
||||
) -> Option<String> {
|
||||
let git_root = resolve_root_git_project_for_trust(cwd);
|
||||
let git_root = resolve_root_git_project_for_trust(cwd).await;
|
||||
let cwd_tree = render_tree(cwd);
|
||||
let git_root_tree = git_root
|
||||
.as_ref()
|
||||
@@ -412,10 +423,11 @@ fn format_section(title: &str, body: Option<String>, budget_tokens: usize) -> Op
|
||||
fn format_thread_group(
|
||||
current_group: &Path,
|
||||
group: &Path,
|
||||
is_git_repo: bool,
|
||||
entries: Vec<&ThreadMetadata>,
|
||||
) -> Option<String> {
|
||||
let latest = entries.first()?;
|
||||
let group_label = if resolve_root_git_project_for_trust(latest.cwd.as_path()).is_some() {
|
||||
let group_label = if is_git_repo {
|
||||
format!("### Git repo: {}", group.display())
|
||||
} else {
|
||||
format!("### Directory: {}", group.display())
|
||||
|
||||
@@ -43,30 +43,31 @@ fn thread_metadata(cwd: &str, title: &str, first_user_message: &str) -> ThreadMe
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_section_requires_meaningful_structure() {
|
||||
#[tokio::test]
|
||||
async fn workspace_section_requires_meaningful_structure() {
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
assert_eq!(
|
||||
build_workspace_section_with_user_root(cwd.path(), None),
|
||||
build_workspace_section_with_user_root(cwd.path(), None).await,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_section_includes_tree_when_entries_exist() {
|
||||
#[tokio::test]
|
||||
async fn workspace_section_includes_tree_when_entries_exist() {
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
fs::create_dir(cwd.path().join("docs")).expect("create docs dir");
|
||||
fs::write(cwd.path().join("README.md"), "hello").expect("write readme");
|
||||
|
||||
let section =
|
||||
build_workspace_section_with_user_root(cwd.path(), None).expect("workspace section");
|
||||
let section = build_workspace_section_with_user_root(cwd.path(), None)
|
||||
.await
|
||||
.expect("workspace section");
|
||||
assert!(section.contains("Working directory tree:"));
|
||||
assert!(section.contains("- docs/"));
|
||||
assert!(section.contains("- README.md"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_section_includes_user_root_tree_when_distinct() {
|
||||
#[tokio::test]
|
||||
async fn workspace_section_includes_user_root_tree_when_distinct() {
|
||||
let root = TempDir::new().expect("tempdir");
|
||||
let cwd = root.path().join("cwd");
|
||||
let git_root = root.path().join("git");
|
||||
@@ -80,14 +81,15 @@ fn workspace_section_includes_user_root_tree_when_distinct() {
|
||||
fs::write(user_root.join(".zshrc"), "export TEST=1").expect("write home file");
|
||||
|
||||
let section = build_workspace_section_with_user_root(cwd.as_path(), Some(user_root))
|
||||
.await
|
||||
.expect("workspace section");
|
||||
assert!(section.contains("User root tree:"));
|
||||
assert!(section.contains("- code/"));
|
||||
assert!(!section.contains("- .zshrc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recent_work_section_groups_threads_by_cwd() {
|
||||
#[tokio::test]
|
||||
async fn recent_work_section_groups_threads_by_cwd() {
|
||||
let root = TempDir::new().expect("tempdir");
|
||||
let repo = root.path().join("repo");
|
||||
let workspace_a = repo.join("workspace-a");
|
||||
@@ -123,6 +125,7 @@ fn recent_work_section_groups_threads_by_cwd() {
|
||||
let repo = fs::canonicalize(repo).expect("canonicalize repo");
|
||||
|
||||
let section = build_recent_work_section(current_cwd.as_path(), &recent_threads)
|
||||
.await
|
||||
.expect("recent work section");
|
||||
assert!(section.contains(&format!("### Git repo: {}", repo.display())));
|
||||
assert!(section.contains("Recent sessions: 2"));
|
||||
|
||||
@@ -197,7 +197,7 @@ async fn backfill_sessions_resumes_from_watermark_and_marks_complete() {
|
||||
))
|
||||
.await;
|
||||
|
||||
let mut config = crate::config::test_config();
|
||||
let mut config = crate::config::test_config().await;
|
||||
config.codex_home = codex_home.clone();
|
||||
config.model_provider_id = "test-provider".to_string();
|
||||
backfill_sessions(runtime.as_ref(), &config).await;
|
||||
@@ -267,7 +267,7 @@ async fn backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_f
|
||||
.await
|
||||
.expect("existing metadata upsert");
|
||||
|
||||
let mut config = crate::config::test_config();
|
||||
let mut config = crate::config::test_config().await;
|
||||
config.codex_home = codex_home.clone();
|
||||
config.model_provider_id = "test-provider".to_string();
|
||||
backfill_sessions(runtime.as_ref(), &config).await;
|
||||
@@ -304,7 +304,7 @@ async fn backfill_sessions_normalizes_cwd_before_upsert() {
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let mut config = crate::config::test_config();
|
||||
let mut config = crate::config::test_config().await;
|
||||
config.codex_home = codex_home.clone();
|
||||
config.model_provider_id = "test-provider".to_string();
|
||||
backfill_sessions(runtime.as_ref(), &config).await;
|
||||
|
||||
@@ -234,7 +234,7 @@ async fn ignores_session_prefix_messages_when_truncating() {
|
||||
#[tokio::test]
|
||||
async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
@@ -273,7 +273,7 @@ async fn new_uses_configured_openai_provider_for_model_refresh() {
|
||||
let models_mock = mount_models_once(&server, ModelsResponse { models: vec![] }).await;
|
||||
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
@@ -406,7 +406,7 @@ fn mixed_response_and_legacy_user_event_history_is_mid_turn() {
|
||||
#[tokio::test]
|
||||
async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_history() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
@@ -503,7 +503,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
|
||||
#[tokio::test]
|
||||
async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
@@ -589,7 +589,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
#[tokio::test]
|
||||
async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_source() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config();
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home");
|
||||
config.cwd = config.codex_home.clone();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
@@ -54,8 +54,8 @@ fn windows_shell_safety_description() -> String {
|
||||
format!("\n\n{}", super::windows_destructive_filesystem_guidance())
|
||||
}
|
||||
|
||||
fn search_capable_model_info() -> ModelInfo {
|
||||
let config = test_config();
|
||||
async fn search_capable_model_info() -> ModelInfo {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.supports_search_tool = true;
|
||||
@@ -358,8 +358,8 @@ fn strip_descriptions_tool(spec: &mut ToolSpec) {
|
||||
}
|
||||
}
|
||||
|
||||
fn model_info_from_models_json(slug: &str) -> ModelInfo {
|
||||
let config = test_config();
|
||||
async fn model_info_from_models_json(slug: &str) -> ModelInfo {
|
||||
let config = test_config().await;
|
||||
let response: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
let model = response
|
||||
@@ -394,9 +394,9 @@ fn unified_exec_is_blocked_for_windows_sandboxed_policies_only() {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_provided_unified_exec_is_blocked_for_windows_sandboxed_policies() {
|
||||
let mut model_info = model_info_from_models_json("gpt-5-codex");
|
||||
#[tokio::test]
|
||||
async fn model_provided_unified_exec_is_blocked_for_windows_sandboxed_policies() {
|
||||
let mut model_info = model_info_from_models_json("gpt-5-codex").await;
|
||||
model_info.shell_type = ConfigShellToolType::UnifiedExec;
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -418,9 +418,9 @@ fn model_provided_unified_exec_is_blocked_for_windows_sandboxed_policies() {
|
||||
assert_eq!(config.shell_type, expected_shell_type);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
let model_info = model_info_from_models_json("gpt-5-codex");
|
||||
#[tokio::test]
|
||||
async fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
let model_info = model_info_from_models_json("gpt-5-codex").await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -503,9 +503,9 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_collab_tools_enabled() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_collab_tools_enabled() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Collab);
|
||||
@@ -528,9 +528,9 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
assert_lacks_tool_name(&tools, "list_agents");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Collab);
|
||||
@@ -680,9 +680,9 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
assert_lacks_tool_name(&tools, "resume_agent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::SpawnCsv);
|
||||
@@ -710,9 +710,9 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.supports_image_detail_original = true;
|
||||
@@ -738,9 +738,9 @@ fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
assert!(!properties.contains_key("detail"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.supports_image_detail_original = true;
|
||||
@@ -775,9 +775,9 @@ fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
assert!(description.contains("omit this field for default resized behavior"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_artifact_tool_enabled() {
|
||||
let mut config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_artifact_tool_enabled() {
|
||||
let mut config = test_config().await;
|
||||
let runtime_root = tempfile::TempDir::new().expect("create temp codex home");
|
||||
config.codex_home = runtime_root.path().to_path_buf();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
@@ -797,9 +797,9 @@ fn test_build_specs_artifact_tool_enabled() {
|
||||
assert_contains_tool_names(&tools, &["artifacts"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::SpawnCsv);
|
||||
@@ -833,9 +833,9 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
assert_lacks_tool_name(&tools, "request_user_input");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -876,9 +876,9 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_requires_feature_flag() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn request_permissions_requires_feature_flag() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -914,9 +914,9 @@ fn request_permissions_requires_feature_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ExecPermissionApprovals);
|
||||
@@ -935,9 +935,9 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
assert_lacks_tool_name(&tools, "request_permissions");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_memory_requires_feature_flag() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn get_memory_requires_feature_flag() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.disable(Feature::MemoryTool);
|
||||
@@ -958,9 +958,9 @@ fn get_memory_requires_feature_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_repl_requires_feature_flag() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn js_repl_requires_feature_flag() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
|
||||
@@ -986,9 +986,9 @@ fn js_repl_requires_feature_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_repl_enabled_adds_tools() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn js_repl_enabled_adds_tools() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::JsRepl);
|
||||
@@ -1007,9 +1007,9 @@ fn js_repl_enabled_adds_tools() {
|
||||
assert_contains_tool_names(&tools, &["js_repl", "js_repl_reset"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_generation_tools_require_feature_and_supported_model() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn image_generation_tools_require_feature_and_supported_model() {
|
||||
let config = test_config().await;
|
||||
let mut supported_model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5.2", &config);
|
||||
supported_model_info.slug = "custom/gpt-5.2-variant".to_string();
|
||||
@@ -1090,14 +1090,14 @@ fn js_repl_freeform_grammar_blocks_common_non_js_prefixes() {
|
||||
assert!(!format.definition.contains("(?!"));
|
||||
}
|
||||
|
||||
fn assert_model_tools(
|
||||
async fn assert_model_tools(
|
||||
model_slug: &str,
|
||||
features: &Features,
|
||||
web_search_mode: Option<WebSearchMode>,
|
||||
expected_tools: &[&str],
|
||||
) {
|
||||
let _config = test_config();
|
||||
let model_info = model_info_from_models_json(model_slug);
|
||||
let _config = test_config().await;
|
||||
let model_info = model_info_from_models_json(model_slug).await;
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
@@ -1125,7 +1125,7 @@ fn assert_model_tools(
|
||||
assert_eq!(&tool_names, &expected_tools,);
|
||||
}
|
||||
|
||||
fn assert_default_model_tools(
|
||||
async fn assert_default_model_tools(
|
||||
model_slug: &str,
|
||||
features: &Features,
|
||||
web_search_mode: Option<WebSearchMode>,
|
||||
@@ -1138,12 +1138,12 @@ fn assert_default_model_tools(
|
||||
vec![shell_tool]
|
||||
};
|
||||
expected.extend(expected_tail);
|
||||
assert_model_tools(model_slug, features, web_search_mode, &expected);
|
||||
assert_model_tools(model_slug, features, web_search_mode, &expected).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
|
||||
@@ -1172,9 +1172,9 @@ fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_mode_live_sets_external_web_access_true() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn web_search_mode_live_sets_external_web_access_true() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
|
||||
@@ -1203,9 +1203,9 @@ fn web_search_mode_live_sets_external_web_access_true() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
let web_search_config = WebSearchConfig {
|
||||
@@ -1252,9 +1252,9 @@ fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
let config = test_config().await;
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
model_info.web_search_tool_type = WebSearchToolType::TextAndImage;
|
||||
@@ -1290,9 +1290,9 @@ fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -1316,9 +1316,9 @@ fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -1343,8 +1343,8 @@ fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_gpt5_codex_default() {
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_gpt5_codex_default() {
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5-codex",
|
||||
@@ -1363,11 +1363,12 @@ fn test_build_specs_gpt5_codex_default() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_gpt51_codex_default() {
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_gpt51_codex_default() {
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1-codex",
|
||||
@@ -1386,11 +1387,12 @@ fn test_build_specs_gpt51_codex_default() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_gpt5_codex_unified_exec_web_search() {
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_gpt5_codex_unified_exec_web_search() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
assert_model_tools(
|
||||
@@ -1411,11 +1413,12 @@ fn test_build_specs_gpt5_codex_unified_exec_web_search() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_gpt51_codex_unified_exec_web_search() {
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_gpt51_codex_unified_exec_web_search() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
assert_model_tools(
|
||||
@@ -1436,11 +1439,12 @@ fn test_build_specs_gpt51_codex_unified_exec_web_search() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_1_codex_max_defaults() {
|
||||
#[tokio::test]
|
||||
async fn test_gpt_5_1_codex_max_defaults() {
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1-codex-max",
|
||||
@@ -1459,11 +1463,12 @@ fn test_gpt_5_1_codex_max_defaults() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codex_5_1_mini_defaults() {
|
||||
#[tokio::test]
|
||||
async fn test_codex_5_1_mini_defaults() {
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1-codex-mini",
|
||||
@@ -1482,11 +1487,12 @@ fn test_codex_5_1_mini_defaults() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_defaults() {
|
||||
#[tokio::test]
|
||||
async fn test_gpt_5_defaults() {
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5",
|
||||
@@ -1504,11 +1510,12 @@ fn test_gpt_5_defaults() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_1_defaults() {
|
||||
#[tokio::test]
|
||||
async fn test_gpt_5_1_defaults() {
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1",
|
||||
@@ -1527,11 +1534,12 @@ fn test_gpt_5_1_defaults() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
#[tokio::test]
|
||||
async fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
assert_model_tools(
|
||||
@@ -1552,12 +1560,13 @@ fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_default_shell_present() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_default_shell_present() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -1581,9 +1590,9 @@ fn test_build_specs_default_shell_present() {
|
||||
assert_contains_tool_names(&tools, &subset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -1644,10 +1653,10 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
fn test_parallel_support_flags() {
|
||||
let config = test_config();
|
||||
async fn test_parallel_support_flags() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -1670,10 +1679,10 @@ fn test_parallel_support_flags() {
|
||||
assert!(find_tool(&tools, "read_file").supports_parallel_tool_calls);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_model_info_includes_sync_tool() {
|
||||
let _config = test_config();
|
||||
let mut model_info = model_info_from_models_json("gpt-5-codex");
|
||||
#[tokio::test]
|
||||
async fn test_test_model_info_includes_sync_tool() {
|
||||
let _config = test_config().await;
|
||||
let mut model_info = model_info_from_models_json("gpt-5-codex").await;
|
||||
model_info.experimental_supported_tools = vec![
|
||||
"test_sync_tool".to_string(),
|
||||
"read_file".to_string(),
|
||||
@@ -1711,9 +1720,9 @@ fn test_test_model_info_includes_sync_tool() {
|
||||
assert!(tools.iter().any(|tool| tool_name(&tool.spec) == "list_dir"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_mcp_tools_converted() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_mcp_tools_converted() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -1804,9 +1813,9 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -1853,9 +1862,9 @@ fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
assert_eq!(mcp_names, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
let available_models = Vec::new();
|
||||
@@ -1975,9 +1984,9 @@ fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
assert!(!description.contains("mcp__rmcp__echo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_requires_model_capability_only() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn search_tool_requires_model_capability_only() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let app_tools = Some(HashMap::from([(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
@@ -2026,9 +2035,9 @@ fn search_tool_requires_model_capability_only() {
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
features.enable(Feature::Plugins);
|
||||
@@ -2062,9 +2071,9 @@ fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let discoverable_tools = Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -2107,10 +2116,9 @@ fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_description_handles_no_enabled_apps() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn search_tool_description_handles_no_enabled_apps() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
let available_models = Vec::new();
|
||||
@@ -2134,9 +2142,9 @@ fn search_tool_description_handles_no_enabled_apps() {
|
||||
assert!(!description.contains("{{app_descriptions}}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_description_falls_back_to_connector_name_without_description() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn search_tool_description_falls_back_to_connector_name_without_description() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
let available_models = Vec::new();
|
||||
@@ -2182,9 +2190,9 @@ fn search_tool_description_falls_back_to_connector_name_without_description() {
|
||||
assert!(!description.contains("- Calendar:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
let available_models = Vec::new();
|
||||
@@ -2247,9 +2255,9 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
assert!(registry.has_handler(alias.as_str(), None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_suggest_description_lists_discoverable_tools() {
|
||||
let model_info = search_capable_model_info();
|
||||
#[tokio::test]
|
||||
async fn tool_suggest_description_lists_discoverable_tools() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
features.enable(Feature::Plugins);
|
||||
@@ -2329,9 +2337,9 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2389,9 +2397,9 @@ fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mcp_tool_integer_normalized_to_number() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_mcp_tool_integer_normalized_to_number() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2445,9 +2453,9 @@ fn test_mcp_tool_integer_normalized_to_number() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2505,9 +2513,9 @@ fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2730,9 +2738,9 @@ Examples of valid command strings:
|
||||
assert_eq!(description, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2840,9 +2848,9 @@ fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
@@ -2871,9 +2879,9 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
@@ -2923,8 +2931,8 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_only_restricts_model_tools_to_exec_tools() {
|
||||
#[tokio::test]
|
||||
async fn code_mode_only_restricts_model_tools_to_exec_tools() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::CodeModeOnly);
|
||||
@@ -2934,12 +2942,13 @@ fn code_mode_only_restricts_model_tools_to_exec_tools() {
|
||||
&features,
|
||||
Some(WebSearchMode::Live),
|
||||
&["exec", "wait"],
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
@@ -2970,9 +2979,9 @@ fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
assert!(description.contains("### `view_image` (`view_image`)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only() {
|
||||
let config = test_config();
|
||||
#[tokio::test]
|
||||
async fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only() {
|
||||
let config = test_config().await;
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::sync::Arc;
|
||||
|
||||
use crate::ExecServerClient;
|
||||
use crate::ExecServerError;
|
||||
use crate::LOCAL_FS;
|
||||
use crate::RemoteExecServerConnectArgs;
|
||||
use crate::file_system::ExecutorFileSystem;
|
||||
use crate::local_file_system::LocalFileSystem;
|
||||
use crate::local_process::LocalProcess;
|
||||
use crate::process::ExecProcess;
|
||||
use crate::remote_file_system::RemoteFileSystem;
|
||||
@@ -101,7 +101,7 @@ impl Environment {
|
||||
if let Some(client) = self.remote_exec_server_client.clone() {
|
||||
Arc::new(RemoteFileSystem::new(client))
|
||||
} else {
|
||||
Arc::new(LocalFileSystem)
|
||||
Arc::new(LOCAL_FS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ pub type FileSystemResult<T> = io::Result<T>;
|
||||
pub trait ExecutorFileSystem: Send + Sync {
|
||||
async fn read_file(&self, path: &AbsolutePathBuf) -> FileSystemResult<Vec<u8>>;
|
||||
|
||||
async fn read_to_string(&self, path: &AbsolutePathBuf) -> FileSystemResult<String> {
|
||||
let bytes = self.read_file(path).await?;
|
||||
String::from_utf8(bytes).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
|
||||
}
|
||||
|
||||
async fn write_file(&self, path: &AbsolutePathBuf, contents: Vec<u8>) -> FileSystemResult<()>;
|
||||
|
||||
async fn create_directory(
|
||||
|
||||
@@ -39,6 +39,7 @@ pub use file_system::FileMetadata;
|
||||
pub use file_system::FileSystemResult;
|
||||
pub use file_system::ReadDirectoryEntry;
|
||||
pub use file_system::RemoveOptions;
|
||||
pub use local_file_system::LocalFileSystem;
|
||||
pub use process::ExecProcess;
|
||||
pub use process::ExecServerEvent;
|
||||
pub use protocol::ExecExitedNotification;
|
||||
@@ -58,3 +59,5 @@ pub use server::DEFAULT_LISTEN_URL;
|
||||
pub use server::ExecServerListenUrlParseError;
|
||||
pub use server::run_main;
|
||||
pub use server::run_main_with_listen_url;
|
||||
|
||||
pub const LOCAL_FS: LocalFileSystem = LocalFileSystem;
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::RemoveOptions;
|
||||
const MAX_READ_FILE_BYTES: u64 = 512 * 1024 * 1024;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct LocalFileSystem;
|
||||
pub struct LocalFileSystem;
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for LocalFileSystem {
|
||||
|
||||
@@ -618,7 +618,7 @@ async fn diff_against_sha(cwd: &Path, sha: &GitSha) -> Option<String> {
|
||||
/// `[get_git_repo_root]`, but resolves to the root of the main
|
||||
/// repository. Handles worktrees via filesystem inspection without invoking
|
||||
/// the `git` executable.
|
||||
pub fn resolve_root_git_project_for_trust(cwd: &Path) -> Option<PathBuf> {
|
||||
pub async fn resolve_root_git_project_for_trust(cwd: &Path) -> Option<PathBuf> {
|
||||
let base = if cwd.is_dir() { cwd } else { cwd.parent()? };
|
||||
let (repo_root, dot_git) = find_ancestor_git_entry(base)?;
|
||||
if dot_git.is_dir() {
|
||||
|
||||
@@ -2,6 +2,7 @@ use codex_core::AuthManager;
|
||||
use codex_core::config::Config;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -74,7 +75,7 @@ pub(crate) struct OnboardingResult {
|
||||
}
|
||||
|
||||
impl OnboardingScreen {
|
||||
pub(crate) fn new(tui: &mut Tui, args: OnboardingScreenArgs) -> Self {
|
||||
pub(crate) async fn new(tui: &mut Tui, args: OnboardingScreenArgs) -> Self {
|
||||
let OnboardingScreenArgs {
|
||||
show_trust_screen,
|
||||
show_login_screen,
|
||||
@@ -117,15 +118,18 @@ impl OnboardingScreen {
|
||||
WindowsSandboxLevel::from_config(&config) == WindowsSandboxLevel::Disabled;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let show_windows_create_sandbox_hint = false;
|
||||
let highlighted = TrustDirectorySelection::Trust;
|
||||
if show_trust_screen {
|
||||
let trust_target = resolve_root_git_project_for_trust(&cwd)
|
||||
.await
|
||||
.unwrap_or_else(|| cwd.clone());
|
||||
steps.push(Step::TrustDirectory(TrustDirectoryWidget {
|
||||
cwd,
|
||||
codex_home,
|
||||
cwd,
|
||||
trust_target,
|
||||
show_windows_create_sandbox_hint,
|
||||
should_quit: false,
|
||||
selection: None,
|
||||
highlighted,
|
||||
highlighted: TrustDirectorySelection::Trust,
|
||||
error: None,
|
||||
}))
|
||||
}
|
||||
@@ -398,7 +402,7 @@ pub(crate) async fn run_onboarding_app(
|
||||
) -> Result<OnboardingResult> {
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
let mut onboarding_screen = OnboardingScreen::new(tui, args);
|
||||
let mut onboarding_screen = OnboardingScreen::new(tui, args).await;
|
||||
// One-time guard to fully clear the screen after ChatGPT login success message is shown
|
||||
let mut did_full_clear_after_success = false;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_core::config::set_project_trust_level;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -27,6 +26,7 @@ use super::onboarding_screen::StepState;
|
||||
pub(crate) struct TrustDirectoryWidget {
|
||||
pub codex_home: PathBuf,
|
||||
pub cwd: PathBuf,
|
||||
pub trust_target: PathBuf,
|
||||
pub show_windows_create_sandbox_hint: bool,
|
||||
pub should_quit: bool,
|
||||
pub selection: Option<TrustDirectorySelection>,
|
||||
@@ -142,13 +142,18 @@ impl StepStateProvider for TrustDirectoryWidget {
|
||||
|
||||
impl TrustDirectoryWidget {
|
||||
fn handle_trust(&mut self) {
|
||||
let target =
|
||||
resolve_root_git_project_for_trust(&self.cwd).unwrap_or_else(|| self.cwd.clone());
|
||||
if let Err(e) = set_project_trust_level(&self.codex_home, &target, TrustLevel::Trusted) {
|
||||
if let Err(e) =
|
||||
set_project_trust_level(&self.codex_home, &self.trust_target, TrustLevel::Trusted)
|
||||
{
|
||||
tracing::error!("Failed to set project trusted: {e:?}");
|
||||
self.error = Some(format!("Failed to set trust for {}: {e}", target.display()));
|
||||
self.error = Some(format!(
|
||||
"Failed to set trust for {}: {e}",
|
||||
self.trust_target.display()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
self.error = None;
|
||||
self.selection = Some(TrustDirectorySelection::Trust);
|
||||
}
|
||||
|
||||
@@ -182,6 +187,7 @@ mod tests {
|
||||
let mut widget = TrustDirectoryWidget {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
cwd: PathBuf::from("."),
|
||||
trust_target: PathBuf::from("."),
|
||||
show_windows_create_sandbox_hint: false,
|
||||
should_quit: false,
|
||||
selection: None,
|
||||
@@ -207,6 +213,7 @@ mod tests {
|
||||
let widget = TrustDirectoryWidget {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
cwd: PathBuf::from("/workspace/project"),
|
||||
trust_target: PathBuf::from("/workspace/project"),
|
||||
show_windows_create_sandbox_hint: false,
|
||||
should_quit: false,
|
||||
selection: None,
|
||||
|
||||
@@ -4,6 +4,7 @@ use codex_app_server_protocol::ServerNotification;
|
||||
use codex_core::config::Config;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -77,7 +78,7 @@ pub(crate) struct OnboardingResult {
|
||||
}
|
||||
|
||||
impl OnboardingScreen {
|
||||
pub(crate) fn new(tui: &mut Tui, args: OnboardingScreenArgs) -> Self {
|
||||
pub(crate) async fn new(tui: &mut Tui, args: OnboardingScreenArgs) -> Self {
|
||||
let OnboardingScreenArgs {
|
||||
show_trust_screen,
|
||||
show_login_screen,
|
||||
@@ -124,15 +125,18 @@ impl OnboardingScreen {
|
||||
WindowsSandboxLevel::from_config(&config) == WindowsSandboxLevel::Disabled;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let show_windows_create_sandbox_hint = false;
|
||||
let highlighted = TrustDirectorySelection::Trust;
|
||||
if show_trust_screen {
|
||||
let trust_target = resolve_root_git_project_for_trust(&cwd)
|
||||
.await
|
||||
.unwrap_or_else(|| cwd.clone());
|
||||
steps.push(Step::TrustDirectory(TrustDirectoryWidget {
|
||||
cwd,
|
||||
codex_home,
|
||||
cwd,
|
||||
trust_target,
|
||||
show_windows_create_sandbox_hint,
|
||||
should_quit: false,
|
||||
selection: None,
|
||||
highlighted,
|
||||
highlighted: TrustDirectorySelection::Trust,
|
||||
error: None,
|
||||
}))
|
||||
}
|
||||
@@ -438,7 +442,7 @@ pub(crate) async fn run_onboarding_app(
|
||||
) -> Result<OnboardingResult> {
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
let mut onboarding_screen = OnboardingScreen::new(tui, args);
|
||||
let mut onboarding_screen = OnboardingScreen::new(tui, args).await;
|
||||
// One-time guard to fully clear the screen after ChatGPT login success message is shown
|
||||
let mut did_full_clear_after_success = false;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_core::config::set_project_trust_level;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
@@ -27,6 +26,7 @@ use super::onboarding_screen::StepState;
|
||||
pub(crate) struct TrustDirectoryWidget {
|
||||
pub codex_home: PathBuf,
|
||||
pub cwd: PathBuf,
|
||||
pub trust_target: PathBuf,
|
||||
pub show_windows_create_sandbox_hint: bool,
|
||||
pub should_quit: bool,
|
||||
pub selection: Option<TrustDirectorySelection>,
|
||||
@@ -142,13 +142,18 @@ impl StepStateProvider for TrustDirectoryWidget {
|
||||
|
||||
impl TrustDirectoryWidget {
|
||||
fn handle_trust(&mut self) {
|
||||
let target =
|
||||
resolve_root_git_project_for_trust(&self.cwd).unwrap_or_else(|| self.cwd.clone());
|
||||
if let Err(e) = set_project_trust_level(&self.codex_home, &target, TrustLevel::Trusted) {
|
||||
if let Err(e) =
|
||||
set_project_trust_level(&self.codex_home, &self.trust_target, TrustLevel::Trusted)
|
||||
{
|
||||
tracing::error!("Failed to set project trusted: {e:?}");
|
||||
self.error = Some(format!("Failed to set trust for {}: {e}", target.display()));
|
||||
self.error = Some(format!(
|
||||
"Failed to set trust for {}: {e}",
|
||||
self.trust_target.display()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
self.error = None;
|
||||
self.selection = Some(TrustDirectorySelection::Trust);
|
||||
}
|
||||
|
||||
@@ -182,6 +187,7 @@ mod tests {
|
||||
let mut widget = TrustDirectoryWidget {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
cwd: PathBuf::from("."),
|
||||
trust_target: PathBuf::from("."),
|
||||
show_windows_create_sandbox_hint: false,
|
||||
should_quit: false,
|
||||
selection: None,
|
||||
@@ -207,6 +213,7 @@ mod tests {
|
||||
let widget = TrustDirectoryWidget {
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
cwd: PathBuf::from("/workspace/project"),
|
||||
trust_target: PathBuf::from("/workspace/project"),
|
||||
show_windows_create_sandbox_hint: false,
|
||||
should_quit: false,
|
||||
selection: None,
|
||||
|
||||
Reference in New Issue
Block a user