chore: enforce argument-comment lint across all targets

This commit is contained in:
Michael Bolin
2026-03-27 17:21:27 -07:00
parent f4f6eca871
commit 880a15a281
307 changed files with 7716 additions and 4702 deletions
+5
View File
@@ -154,8 +154,13 @@ jobs:
components: llvm-tools-preview, rustc-dev, rust-src
- uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
- name: Run argument comment lint on codex-rs
if: ${{ runner.os == 'macOS' }}
shell: bash
run: ./tools/argument-comment-lint/run-prebuilt-linter.sh
- name: Run argument comment lint on codex-rs (default targets only)
if: ${{ runner.os != 'macOS' }}
shell: bash
run: ./tools/argument-comment-lint/run-prebuilt-linter.sh -- --lib --bins
# --- CI to validate on different os/targets --------------------------------
lint_build:
@@ -49,7 +49,11 @@ fn normalize_path_for_skill_id_repo_scoped_uses_relative_path() {
fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() {
let skill_path = PathBuf::from("/Users/abc/.codex/skills/doc/SKILL.md");
let path = normalize_path_for_skill_id(None, None, skill_path.as_path());
let path = normalize_path_for_skill_id(
/*repo_url*/ None,
/*repo_root*/ None,
skill_path.as_path(),
);
let expected = expected_absolute_path(&skill_path);
assert_eq!(path, expected);
@@ -59,7 +63,11 @@ fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() {
fn normalize_path_for_skill_id_admin_scoped_uses_absolute_path() {
let skill_path = PathBuf::from("/etc/codex/skills/doc/SKILL.md");
let path = normalize_path_for_skill_id(None, None, skill_path.as_path());
let path = normalize_path_for_skill_id(
/*repo_url*/ None,
/*repo_root*/ None,
skill_path.as_path(),
);
let expected = expected_absolute_path(&skill_path);
assert_eq!(path, expected);
+3 -2
View File
@@ -917,7 +917,7 @@ mod tests {
+ 'static,
Fut: std::future::Future<Output = ()> + Send + 'static,
{
start_test_remote_server_with_auth(None, handler).await
start_test_remote_server_with_auth(/*expected_auth_token*/ None, handler).await
}
async fn start_test_remote_server_with_auth<F, Fut>(
@@ -1164,7 +1164,8 @@ mod tests {
#[tokio::test]
async fn tiny_channel_capacity_still_supports_request_roundtrip() {
let client = start_test_client_with_capacity(SessionSource::Exec, 1).await;
let client =
start_test_client_with_capacity(SessionSource::Exec, /*channel_capacity*/ 1).await;
let _response: ConfigRequirementsReadResponse = client
.request_typed(ClientRequest::ConfigRequirementsRead {
request_id: RequestId::Integer(1),
+1 -1
View File
@@ -2690,7 +2690,7 @@ export type Config = { stableField: Keep, unstableField: string | null } & ({ [k
fn generate_json_filters_experimental_fields_and_methods() -> Result<()> {
let output_dir = std::env::temp_dir().join(format!("codex_schema_{}", Uuid::now_v7()));
fs::create_dir(&output_dir)?;
generate_json_with_experimental(&output_dir, false)?;
generate_json_with_experimental(&output_dir, /*experimental_api*/ false)?;
let thread_start_json =
fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.json"))?;
@@ -23,7 +23,7 @@ fn typescript_schema_fixtures_match_generated() -> Result<()> {
#[test]
fn json_schema_fixtures_match_generated() -> Result<()> {
assert_schema_fixtures_match_generated("json", |output_dir| {
generate_json_with_experimental(output_dir, false)
generate_json_with_experimental(output_dir, /*experimental_api*/ false)
})
}
@@ -8747,7 +8747,7 @@ mod tests {
fn config_load_error_marks_non_auth_cloud_requirements_failures_without_relogin() {
let err = std::io::Error::other(CloudRequirementsLoadError::new(
CloudRequirementsLoadErrorCode::RequestFailed,
None,
/*status_code*/ None,
"failed to load your workspace-managed config",
));
@@ -8961,7 +8961,8 @@ mod tests {
fn merge_persisted_resume_metadata_skips_missing_values() -> Result<()> {
let mut request_overrides = None;
let mut typesafe_overrides = ConfigOverrides::default();
let persisted_metadata = test_thread_metadata(None, None)?;
let persisted_metadata =
test_thread_metadata(/*model*/ None, /*reasoning_effort*/ None)?;
merge_persisted_resume_metadata(
&mut request_overrides,
@@ -9013,7 +9014,7 @@ mod tests {
path.clone(),
&head,
&session_meta,
None,
/*git*/ None,
"test-provider",
timestamp.clone(),
)
@@ -9223,9 +9224,9 @@ mod tests {
source,
Some("atlas".to_string()),
Some("explorer".to_string()),
None,
None,
None,
/*git_sha*/ None,
/*git_branch*/ None,
/*git_origin_url*/ None,
);
let thread = summary_to_thread(summary);
@@ -9244,7 +9245,9 @@ mod tests {
manager.connection_initialized(connection).await;
manager
.try_ensure_connection_subscribed(thread_id, connection, false)
.try_ensure_connection_subscribed(
thread_id, connection, /*experimental_raw_events*/ false,
)
.await
.expect("connection should be live");
{
@@ -9288,11 +9291,19 @@ mod tests {
manager.connection_initialized(connection_a).await;
manager.connection_initialized(connection_b).await;
manager
.try_ensure_connection_subscribed(thread_id, connection_a, false)
.try_ensure_connection_subscribed(
thread_id,
connection_a,
/*experimental_raw_events*/ false,
)
.await
.expect("connection_a should be live");
manager
.try_ensure_connection_subscribed(thread_id, connection_b, false)
.try_ensure_connection_subscribed(
thread_id,
connection_b,
/*experimental_raw_events*/ false,
)
.await
.expect("connection_b should be live");
{
@@ -9325,7 +9336,9 @@ mod tests {
assert!(
manager
.try_ensure_connection_subscribed(thread_id, connection, false)
.try_ensure_connection_subscribed(
thread_id, connection, /*experimental_raw_events*/ false
)
.await
.is_none()
);
@@ -137,7 +137,7 @@ mod tests {
&all_connectors,
&[],
&[AppConnectorId("alpha".to_string())],
false,
/*codex_apps_ready*/ false,
),
Vec::new()
);
+1 -1
View File
@@ -90,7 +90,7 @@ mod tests {
#[test]
fn compute_source_filters_defaults_to_interactive_sources() {
let (allowed_sources, filter) = compute_source_filters(None);
let (allowed_sources, filter) = compute_source_filters(/*source_kinds*/ None);
assert_eq!(allowed_sources, INTERACTIVE_SESSION_SOURCES.to_vec());
assert_eq!(filter, None);
+2 -1
View File
@@ -786,7 +786,8 @@ mod tests {
#[tokio::test]
async fn in_process_start_clamps_zero_channel_capacity() {
let client = start_test_client_with_capacity(SessionSource::Cli, 0).await;
let client =
start_test_client_with_capacity(SessionSource::Cli, /*channel_capacity*/ 0).await;
let response = loop {
match client
.request(ClientRequest::ConfigRequirementsRead {
+4 -1
View File
@@ -860,7 +860,10 @@ mod tests {
#[test]
fn log_format_from_env_value_defaults_for_non_json_values() {
assert_eq!(LogFormat::from_env_value(None), LogFormat::Default);
assert_eq!(
LogFormat::from_env_value(/*value*/ None),
LogFormat::Default
);
assert_eq!(LogFormat::from_env_value(Some("")), LogFormat::Default);
assert_eq!(LogFormat::from_env_value(Some("text")), LogFormat::Default);
assert_eq!(LogFormat::from_env_value(Some("jsonl")), LogFormat::Default);
@@ -147,7 +147,7 @@ impl TracingHarness {
}),
},
},
None,
/*trace*/ None,
)
.await;
assert!(harness.session.initialized);
@@ -213,7 +213,7 @@ async fn build_test_config(codex_home: &Path, server_uri: &str) -> Result<Config
codex_home,
server_uri,
&BTreeMap::new(),
8_192,
/*auto_compact_limit*/ 8_192,
Some(false),
"mock_provider",
"compact",
@@ -509,7 +509,9 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() ->
..
} = RemoteTrace::new("00000000000000000000000000000011", "0000000000000022");
let _: ThreadStartResponse = harness.start_thread(20_002, None).await;
let _: ThreadStartResponse = harness
.start_thread(/*request_id*/ 20_002, /*trace*/ None)
.await;
let untraced_spans = wait_for_exported_spans(harness.tracing, |spans| {
spans.iter().any(|span| {
span.span_kind == SpanKind::Server
@@ -538,10 +540,16 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() ->
.span_context
.trace_id(),
);
assert_has_internal_descendant_at_min_depth(&untraced_spans, untraced_server_span, 1);
assert_has_internal_descendant_at_min_depth(
&untraced_spans,
untraced_server_span,
/*min_depth*/ 1,
);
let baseline_len = untraced_spans.len();
let _: ThreadStartResponse = harness.start_thread(20_003, Some(remote_trace)).await;
let _: ThreadStartResponse = harness
.start_thread(/*request_id*/ 20_003, Some(remote_trace))
.await;
let spans = wait_for_new_exported_spans(harness.tracing, baseline_len, |spans| {
spans.iter().any(|span| {
span.span_kind == SpanKind::Server
@@ -561,8 +569,8 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() ->
assert!(server_request_span.parent_span_is_remote);
assert_eq!(server_request_span.span_context.trace_id(), remote_trace_id);
assert_ne!(server_request_span.span_context.span_id(), SpanId::INVALID);
assert_has_internal_descendant_at_min_depth(&spans, server_request_span, 1);
assert_has_internal_descendant_at_min_depth(&spans, server_request_span, 2);
assert_has_internal_descendant_at_min_depth(&spans, server_request_span, /*min_depth*/ 1);
assert_has_internal_descendant_at_min_depth(&spans, server_request_span, /*min_depth*/ 2);
harness.shutdown().await;
Ok(())
@@ -572,7 +580,7 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() ->
async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
let _guard = tracing_test_guard().lock().await;
let mut harness = TracingHarness::new().await?;
let thread_start_response = harness.start_thread(2, None).await;
let thread_start_response = harness.start_thread(/*request_id*/ 2, /*trace*/ None).await;
let thread_id = thread_start_response.thread.id.clone();
harness.reset_tracing();
+3 -3
View File
@@ -886,7 +886,7 @@ mod tests {
.register_request_context(RequestContext::new(
request_id.clone(),
tracing::info_span!("app_server.request", rpc.method = "thread/start"),
None,
/*parent_trace*/ None,
))
.await;
assert_eq!(outgoing.request_context_count().await, 1);
@@ -997,14 +997,14 @@ mod tests {
.register_request_context(RequestContext::new(
closed_connection_request,
tracing::info_span!("app_server.request", rpc.method = "turn/interrupt"),
None,
/*parent_trace*/ None,
))
.await;
outgoing
.register_request_context(RequestContext::new(
open_connection_request,
tracing::info_span!("app_server.request", rpc.method = "turn/start"),
None,
/*parent_trace*/ None,
))
.await;
assert_eq!(outgoing.request_context_count().await, 2);
+8 -4
View File
@@ -530,7 +530,7 @@ mod tests {
#[test]
fn resolves_in_progress_turn_to_active_status() {
let status = resolve_thread_status(ThreadStatus::Idle, true);
let status = resolve_thread_status(ThreadStatus::Idle, /*has_in_progress_turn*/ true);
assert_eq!(
status,
ThreadStatus::Active {
@@ -538,7 +538,8 @@ mod tests {
}
);
let status = resolve_thread_status(ThreadStatus::NotLoaded, true);
let status =
resolve_thread_status(ThreadStatus::NotLoaded, /*has_in_progress_turn*/ true);
assert_eq!(
status,
ThreadStatus::Active {
@@ -550,11 +551,14 @@ mod tests {
#[test]
fn keeps_status_when_no_in_progress_turn() {
assert_eq!(
resolve_thread_status(ThreadStatus::Idle, false),
resolve_thread_status(ThreadStatus::Idle, /*has_in_progress_turn*/ false),
ThreadStatus::Idle
);
assert_eq!(
resolve_thread_status(ThreadStatus::SystemError, false),
resolve_thread_status(
ThreadStatus::SystemError,
/*has_in_progress_turn*/ false
),
ThreadStatus::SystemError
);
}
+40 -11
View File
@@ -506,8 +506,14 @@ mod tests {
}),
);
let tampered = token.replace(".eyJleHAi", ".eyJleHBi");
let err = verify_signed_bearer_token(&tampered, shared_secret, None, None, 30)
.expect_err("tampered jwt should fail");
let err = verify_signed_bearer_token(
&tampered,
shared_secret,
/*issuer*/ None,
/*audience*/ None,
/*max_clock_skew_seconds*/ 30,
)
.expect_err("tampered jwt should fail");
assert_eq!(err.status_code(), StatusCode::UNAUTHORIZED);
}
@@ -522,8 +528,14 @@ mod tests {
"aud": "audience",
}),
);
verify_signed_bearer_token(&token, shared_secret, Some("issuer"), Some("audience"), 30)
.expect("valid signed token should verify");
verify_signed_bearer_token(
&token,
shared_secret,
Some("issuer"),
Some("audience"),
/*max_clock_skew_seconds*/ 30,
)
.expect("valid signed token should verify");
}
#[test]
@@ -536,8 +548,14 @@ mod tests {
"aud": ["other-audience", "audience"],
}),
);
verify_signed_bearer_token(&token, shared_secret, None, Some("audience"), 30)
.expect("jwt audience arrays should verify");
verify_signed_bearer_token(
&token,
shared_secret,
/*issuer*/ None,
Some("audience"),
/*max_clock_skew_seconds*/ 30,
)
.expect("jwt audience arrays should verify");
}
#[test]
@@ -550,9 +568,14 @@ mod tests {
);
let header_segment = URL_SAFE_NO_PAD.encode(br#"{"alg":"none","typ":"JWT"}"#);
let token = format!("{header_segment}.{claims_segment}.");
let err =
verify_signed_bearer_token(&token, b"0123456789abcdef0123456789abcdef", None, None, 30)
.expect_err("alg=none jwt should be rejected");
let err = verify_signed_bearer_token(
&token,
b"0123456789abcdef0123456789abcdef",
/*issuer*/ None,
/*audience*/ None,
/*max_clock_skew_seconds*/ 30,
)
.expect_err("alg=none jwt should be rejected");
assert_eq!(err.status_code(), StatusCode::UNAUTHORIZED);
}
@@ -565,8 +588,14 @@ mod tests {
"iss": "issuer",
}),
);
let err = verify_signed_bearer_token(&token, shared_secret, None, None, 30)
.expect_err("jwt without exp should be rejected");
let err = verify_signed_bearer_token(
&token,
shared_secret,
/*issuer*/ None,
/*audience*/ None,
/*max_clock_skew_seconds*/ 30,
)
.expect_err("jwt without exp should be rejected");
assert_eq!(err.status_code(), StatusCode::UNAUTHORIZED);
}
+6 -6
View File
@@ -646,7 +646,7 @@ mod tests {
initialized,
Arc::new(AtomicBool::new(true)),
opted_out_notification_methods,
None,
/*disconnect_sender*/ None,
),
);
@@ -686,7 +686,7 @@ mod tests {
Arc::new(AtomicBool::new(true)),
Arc::new(AtomicBool::new(true)),
Arc::new(RwLock::new(HashSet::from(["configWarning".to_string()]))),
None,
/*disconnect_sender*/ None,
),
);
@@ -726,7 +726,7 @@ mod tests {
Arc::new(AtomicBool::new(true)),
Arc::new(AtomicBool::new(true)),
Arc::new(RwLock::new(HashSet::new())),
None,
/*disconnect_sender*/ None,
),
);
@@ -772,7 +772,7 @@ mod tests {
Arc::new(AtomicBool::new(true)),
Arc::new(AtomicBool::new(false)),
Arc::new(RwLock::new(HashSet::new())),
None,
/*disconnect_sender*/ None,
),
);
@@ -834,7 +834,7 @@ mod tests {
Arc::new(AtomicBool::new(true)),
Arc::new(AtomicBool::new(true)),
Arc::new(RwLock::new(HashSet::new())),
None,
/*disconnect_sender*/ None,
),
);
@@ -1006,7 +1006,7 @@ mod tests {
Arc::new(AtomicBool::new(true)),
Arc::new(AtomicBool::new(true)),
Arc::new(RwLock::new(HashSet::new())),
None,
/*disconnect_sender*/ None,
),
);
+1 -1
View File
@@ -160,7 +160,7 @@ async fn get_auth_status_with_api_key() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_auth_status_with_api_key_when_auth_not_required() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml_custom_provider(codex_home.path(), false)?;
create_config_toml_custom_provider(codex_home.path(), /*requires_openai_auth*/ false)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
@@ -45,7 +45,7 @@ async fn get_conversation_summary_by_thread_id_reads_rollout() -> Result<()> {
META_RFC3339,
PREVIEW,
Some(MODEL_PROVIDER),
None,
/*git_info*/ None,
)?;
let thread_id = ThreadId::from_string(&conversation_id)?;
let expected = expected_summary(
@@ -86,7 +86,7 @@ async fn get_conversation_summary_by_relative_rollout_path_resolves_from_codex_h
META_RFC3339,
PREVIEW,
Some(MODEL_PROVIDER),
None,
/*git_info*/ None,
)?;
let thread_id = ThreadId::from_string(&conversation_id)?;
let rollout_path = rollout_path(codex_home.path(), FILENAME_TS, &conversation_id);
@@ -237,7 +237,11 @@ async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> {
let root_path = root.path().to_string_lossy().to_string();
// Send fuzzyFileSearch request.
let request_id = mcp
.send_fuzzy_file_search_request("abe", vec![root_path.clone()], None)
.send_fuzzy_file_search_request(
"abe",
vec![root_path.clone()],
/*cancellation_token*/ None,
)
.await?;
// Read response and verify shape and ordering.
@@ -298,7 +302,11 @@ async fn test_fuzzy_file_search_accepts_cancellation_token() -> Result<()> {
let root_path = root.path().to_string_lossy().to_string();
let request_id = mcp
.send_fuzzy_file_search_request("alp", vec![root_path.clone()], None)
.send_fuzzy_file_search_request(
"alp",
vec![root_path.clone()],
/*cancellation_token*/ None,
)
.await?;
let request_id_2 = mcp
@@ -981,7 +981,7 @@ async fn login_account_chatgpt_device_code_returns_error_when_disabled() -> Resu
},
)?;
write_models_cache(codex_home.path())?;
mock_device_code_usercode_failure(&mock_server, 404).await;
mock_device_code_usercode_failure(&mock_server, /*status*/ 404).await;
let issuer = mock_server.uri();
let mut mcp = McpProcess::new_with_env(
@@ -1038,7 +1038,7 @@ async fn login_account_chatgpt_device_code_succeeds_and_notifies() -> Result<()>
)?;
write_models_cache(codex_home.path())?;
mock_device_code_usercode(&mock_server, 0).await;
mock_device_code_usercode(&mock_server, /*interval_seconds*/ 0).await;
mock_device_code_token_success(&mock_server).await;
let id_token = encode_id_token(
&ChatGptIdTokenClaims::new()
@@ -1122,8 +1122,8 @@ async fn login_account_chatgpt_device_code_failure_notifies_without_account_upda
)?;
write_models_cache(codex_home.path())?;
mock_device_code_usercode(&mock_server, 0).await;
mock_device_code_token_failure(&mock_server, 500).await;
mock_device_code_usercode(&mock_server, /*interval_seconds*/ 0).await;
mock_device_code_token_failure(&mock_server, /*status*/ 500).await;
let issuer = mock_server.uri();
let mut mcp = McpProcess::new_with_env(
@@ -1197,8 +1197,8 @@ async fn login_account_chatgpt_device_code_can_be_cancelled() -> Result<()> {
)?;
write_models_cache(codex_home.path())?;
mock_device_code_usercode(&mock_server, 1).await;
mock_device_code_token_failure(&mock_server, 404).await;
mock_device_code_usercode(&mock_server, /*interval_seconds*/ 1).await;
mock_device_code_token_failure(&mock_server, /*status*/ 404).await;
let issuer = mock_server.uri();
let mut mcp = McpProcess::new_with_env(
@@ -31,7 +31,7 @@ async fn app_server_default_analytics_disabled_without_flag() -> Result<()> {
&config,
SERVICE_VERSION,
Some("codex-app-server"),
false,
/*default_analytics_enabled*/ false,
)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
@@ -56,7 +56,7 @@ async fn app_server_default_analytics_enabled_with_flag() -> Result<()> {
&config,
SERVICE_VERSION,
Some("codex-app-server"),
true,
/*default_analytics_enabled*/ true,
)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
@@ -722,15 +722,15 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
let mut ws1 = connect_websocket(bind_addr).await?;
let mut ws2 = connect_websocket(bind_addr).await?;
send_initialize_request(&mut ws1, 1, "ws_client_one").await?;
read_initialize_response(&mut ws1, 1).await?;
send_initialize_request(&mut ws2, 2, "ws_client_two").await?;
read_initialize_response(&mut ws2, 2).await?;
send_initialize_request(&mut ws1, /*id*/ 1, "ws_client_one").await?;
read_initialize_response(&mut ws1, /*request_id*/ 1).await?;
send_initialize_request(&mut ws2, /*id*/ 2, "ws_client_two").await?;
read_initialize_response(&mut ws2, /*request_id*/ 2).await?;
send_request(
&mut ws1,
"command/exec",
101,
/*id*/ 101,
Some(serde_json::json!({
"command": [
"python3",
@@ -749,12 +749,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
assert_eq!(delta.stream, CommandExecOutputStream::Stdout);
let delta_text = String::from_utf8(STANDARD.decode(&delta.delta_base64)?)?;
assert!(delta_text.contains("ready"));
wait_for_process_marker(&marker, true).await?;
wait_for_process_marker(&marker, /*should_exist*/ true).await?;
send_request(
&mut ws2,
"command/exec/terminate",
102,
/*id*/ 102,
Some(serde_json::json!({
"processId": "shared-process",
})),
@@ -773,12 +773,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate
terminate_error.error.message,
"no active command/exec for process id \"shared-process\""
);
wait_for_process_marker(&marker, true).await?;
wait_for_process_marker(&marker, /*should_exist*/ true).await?;
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
ws1.close(None).await?;
wait_for_process_marker(&marker, false).await?;
wait_for_process_marker(&marker, /*should_exist*/ false).await?;
process
.kill()
@@ -50,19 +50,19 @@ async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()>
let server = responses::start_mock_server().await;
let sse1 = responses::sse(vec![
responses::ev_assistant_message("m1", "FIRST_REPLY"),
responses::ev_completed_with_tokens("r1", 70_000),
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
]);
let sse2 = responses::sse(vec![
responses::ev_assistant_message("m2", "SECOND_REPLY"),
responses::ev_completed_with_tokens("r2", 330_000),
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
]);
let sse3 = responses::sse(vec![
responses::ev_assistant_message("m3", "LOCAL_SUMMARY"),
responses::ev_completed_with_tokens("r3", 200),
responses::ev_completed_with_tokens("r3", /*total_tokens*/ 200),
]);
let sse4 = responses::sse(vec![
responses::ev_assistant_message("m4", "FINAL_REPLY"),
responses::ev_completed_with_tokens("r4", 120),
responses::ev_completed_with_tokens("r4", /*total_tokens*/ 120),
]);
responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
@@ -72,7 +72,7 @@ async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()>
&server.uri(),
&BTreeMap::default(),
AUTO_COMPACT_LIMIT,
None,
/*requires_openai_auth*/ None,
"mock_provider",
COMPACT_PROMPT,
)?;
@@ -110,15 +110,15 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<()
let server = responses::start_mock_server().await;
let sse1 = responses::sse(vec![
responses::ev_assistant_message("m1", "FIRST_REPLY"),
responses::ev_completed_with_tokens("r1", 70_000),
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
]);
let sse2 = responses::sse(vec![
responses::ev_assistant_message("m2", "SECOND_REPLY"),
responses::ev_completed_with_tokens("r2", 330_000),
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
]);
let sse3 = responses::sse(vec![
responses::ev_assistant_message("m3", "FINAL_REPLY"),
responses::ev_completed_with_tokens("r3", 120),
responses::ev_completed_with_tokens("r3", /*total_tokens*/ 120),
]);
let responses_log = responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3]).await;
@@ -197,7 +197,7 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -
let server = responses::start_mock_server().await;
let sse = responses::sse(vec![
responses::ev_assistant_message("m1", "MANUAL_COMPACT_SUMMARY"),
responses::ev_completed_with_tokens("r1", 200),
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 200),
]);
responses::mount_sse_sequence(&server, vec![sse]).await;
@@ -207,7 +207,7 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -
&server.uri(),
&BTreeMap::default(),
AUTO_COMPACT_LIMIT,
None,
/*requires_openai_auth*/ None,
"mock_provider",
COMPACT_PROMPT,
)?;
@@ -257,7 +257,7 @@ async fn thread_compact_start_rejects_invalid_thread_id() -> Result<()> {
&server.uri(),
&BTreeMap::default(),
AUTO_COMPACT_LIMIT,
None,
/*requires_openai_auth*/ None,
"mock_provider",
COMPACT_PROMPT,
)?;
@@ -293,7 +293,7 @@ async fn thread_compact_start_rejects_unknown_thread_id() -> Result<()> {
&server.uri(),
&BTreeMap::default(),
AUTO_COMPACT_LIMIT,
None,
/*requires_openai_auth*/ None,
"mock_provider",
COMPACT_PROMPT,
)?;
@@ -58,27 +58,27 @@ async fn websocket_transport_routes_per_connection_handshake_and_responses() ->
let mut ws1 = connect_websocket(bind_addr).await?;
let mut ws2 = connect_websocket(bind_addr).await?;
send_initialize_request(&mut ws1, 1, "ws_client_one").await?;
let first_init = read_response_for_id(&mut ws1, 1).await?;
send_initialize_request(&mut ws1, /*id*/ 1, "ws_client_one").await?;
let first_init = read_response_for_id(&mut ws1, /*id*/ 1).await?;
assert_eq!(first_init.id, RequestId::Integer(1));
// Initialize responses are request-scoped and must not leak to other
// connections.
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
send_config_read_request(&mut ws2, 2).await?;
let not_initialized = read_error_for_id(&mut ws2, 2).await?;
send_config_read_request(&mut ws2, /*id*/ 2).await?;
let not_initialized = read_error_for_id(&mut ws2, /*id*/ 2).await?;
assert_eq!(not_initialized.error.message, "Not initialized");
send_initialize_request(&mut ws2, 3, "ws_client_two").await?;
let second_init = read_response_for_id(&mut ws2, 3).await?;
send_initialize_request(&mut ws2, /*id*/ 3, "ws_client_two").await?;
let second_init = read_response_for_id(&mut ws2, /*id*/ 3).await?;
assert_eq!(second_init.id, RequestId::Integer(3));
// Same request-id on different connections must route independently.
send_config_read_request(&mut ws1, 77).await?;
send_config_read_request(&mut ws2, 77).await?;
let ws1_config = read_response_for_id(&mut ws1, 77).await?;
let ws2_config = read_response_for_id(&mut ws2, 77).await?;
send_config_read_request(&mut ws1, /*id*/ 77).await?;
send_config_read_request(&mut ws2, /*id*/ 77).await?;
let ws1_config = read_response_for_id(&mut ws1, /*id*/ 77).await?;
let ws2_config = read_response_for_id(&mut ws2, /*id*/ 77).await?;
assert_eq!(ws1_config.id, RequestId::Integer(77));
assert_eq!(ws2_config.id, RequestId::Integer(77));
@@ -108,8 +108,8 @@ async fn websocket_transport_serves_health_endpoints_on_same_listener() -> Resul
assert_eq!(healthz.status(), StatusCode::OK);
let mut ws = connect_websocket(bind_addr).await?;
send_initialize_request(&mut ws, 1, "ws_health_client").await?;
let init = read_response_for_id(&mut ws, 1).await?;
send_initialize_request(&mut ws, /*id*/ 1, "ws_health_client").await?;
let init = read_response_for_id(&mut ws, /*id*/ 1).await?;
assert_eq!(init.id, RequestId::Integer(1));
process
@@ -128,14 +128,14 @@ async fn websocket_transport_rejects_browser_origin_without_auth() -> Result<()>
let (mut process, bind_addr) = spawn_websocket_server(codex_home.path()).await?;
let mut ws = connect_websocket(bind_addr).await?;
send_initialize_request(&mut ws, 1, "ws_loopback_client").await?;
let init = read_response_for_id(&mut ws, 1).await?;
send_initialize_request(&mut ws, /*id*/ 1, "ws_loopback_client").await?;
let init = read_response_for_id(&mut ws, /*id*/ 1).await?;
assert_eq!(init.id, RequestId::Integer(1));
drop(ws);
assert_websocket_connect_rejected_with_headers(
bind_addr,
None,
/*bearer_token*/ None,
Some("https://evil.example"),
StatusCode::FORBIDDEN,
)
@@ -165,12 +165,12 @@ async fn websocket_transport_rejects_missing_and_invalid_capability_tokens() ->
let (mut process, bind_addr) =
spawn_websocket_server_with_args(codex_home.path(), "ws://127.0.0.1:0", &auth_args).await?;
assert_websocket_connect_rejected(bind_addr, None).await?;
assert_websocket_connect_rejected(bind_addr, /*bearer_token*/ None).await?;
assert_websocket_connect_rejected(bind_addr, Some("wrong-token")).await?;
let mut ws = connect_websocket_with_bearer(bind_addr, Some("super-secret-token")).await?;
send_initialize_request(&mut ws, 1, "ws_auth_client").await?;
let init = read_response_for_id(&mut ws, 1).await?;
send_initialize_request(&mut ws, /*id*/ 1, "ws_auth_client").await?;
let init = read_response_for_id(&mut ws, /*id*/ 1).await?;
assert_eq!(init.id, RequestId::Integer(1));
process
@@ -266,8 +266,8 @@ async fn websocket_transport_verifies_signed_short_lived_bearer_tokens() -> Resu
}),
)?;
let mut ws = connect_websocket_with_bearer(bind_addr, Some(valid_token.as_str())).await?;
send_initialize_request(&mut ws, 1, "ws_signed_auth_client").await?;
let init = read_response_for_id(&mut ws, 1).await?;
send_initialize_request(&mut ws, /*id*/ 1, "ws_signed_auth_client").await?;
let init = read_response_for_id(&mut ws, /*id*/ 1).await?;
assert_eq!(init.id, RequestId::Integer(1));
process
@@ -320,8 +320,8 @@ async fn websocket_transport_allows_unauthenticated_non_loopback_startup_by_defa
spawn_websocket_server_with_args(codex_home.path(), "ws://0.0.0.0:0", &[]).await?;
let mut ws = connect_websocket(bind_addr).await?;
send_initialize_request(&mut ws, 1, "ws_non_loopback_default_client").await?;
let init = read_response_for_id(&mut ws, 1).await?;
send_initialize_request(&mut ws, /*id*/ 1, "ws_non_loopback_default_client").await?;
let init = read_response_for_id(&mut ws, /*id*/ 1).await?;
assert_eq!(init.id, RequestId::Integer(1));
process
@@ -411,7 +411,7 @@ pub(super) async fn spawn_websocket_server_with_args(
}
pub(super) async fn connect_websocket(bind_addr: SocketAddr) -> Result<WsClient> {
connect_websocket_with_bearer(bind_addr, None).await
connect_websocket_with_bearer(bind_addr, /*bearer_token*/ None).await
}
pub(super) async fn connect_websocket_with_bearer(
@@ -419,7 +419,7 @@ pub(super) async fn connect_websocket_with_bearer(
bearer_token: Option<&str>,
) -> Result<WsClient> {
let url = format!("ws://{}", connectable_bind_addr(bind_addr));
let request = websocket_request(url.as_str(), bearer_token, None)?;
let request = websocket_request(url.as_str(), bearer_token, /*origin*/ None)?;
let deadline = Instant::now() + Duration::from_secs(10);
loop {
match connect_async(request.clone()).await {
@@ -441,7 +441,7 @@ async fn assert_websocket_connect_rejected(
assert_websocket_connect_rejected_with_headers(
bind_addr,
bearer_token,
None,
/*origin*/ None,
StatusCode::UNAUTHORIZED,
)
.await
@@ -156,16 +156,16 @@ async fn start_ctrl_c_restart_fixture(turn_delay: Duration) -> Result<GracefulCt
let (process, bind_addr) = spawn_websocket_server(codex_home.path()).await?;
let mut ws = connect_websocket(bind_addr).await?;
send_initialize_request(&mut ws, 1, "ws_graceful_shutdown").await?;
let init_response = read_response_for_id(&mut ws, 1).await?;
send_initialize_request(&mut ws, /*id*/ 1, "ws_graceful_shutdown").await?;
let init_response = read_response_for_id(&mut ws, /*id*/ 1).await?;
assert_eq!(init_response.id, RequestId::Integer(1));
send_thread_start_request(&mut ws, 2).await?;
let thread_start_response = read_response_for_id(&mut ws, 2).await?;
send_thread_start_request(&mut ws, /*id*/ 2).await?;
let thread_start_response = read_response_for_id(&mut ws, /*id*/ 2).await?;
let ThreadStartResponse { thread, .. } = to_response(thread_start_response)?;
send_turn_start_request(&mut ws, 3, &thread.id).await?;
let turn_start_response = read_response_for_id(&mut ws, 3).await?;
send_turn_start_request(&mut ws, /*id*/ 3, &thread.id).await?;
let turn_start_response = read_response_for_id(&mut ws, /*id*/ 3).await?;
assert_eq!(turn_start_response.id, RequestId::Integer(3));
wait_for_responses_post(&server, Duration::from_secs(5)).await?;
@@ -56,8 +56,10 @@ fn model_from_preset(preset: &ModelPreset) -> Model {
fn expected_visible_models() -> Vec<Model> {
// Filter by supported_in_api to support testing with both ChatGPT and non-ChatGPT auth modes.
let mut presets =
ModelPreset::filter_by_auth(codex_core::test_support::all_model_presets().clone(), false);
let mut presets = ModelPreset::filter_by_auth(
codex_core::test_support::all_model_presets().clone(),
/*chatgpt_mode*/ false,
);
// Mirror `ModelsManager::build_available_models()` default selection after auth filtering.
ModelPreset::mark_default_by_picker_visibility(&mut presets);
@@ -59,7 +59,7 @@ async fn plan_mode_uses_proposed_plan_block_for_plan_item() -> Result<()> {
let turn = start_plan_mode_turn(&mut mcp).await?;
let (_, completed_items, plan_deltas, turn_completed) =
collect_turn_notifications(&mut mcp).await?;
wait_for_responses_request_count(&server, 1).await?;
wait_for_responses_request_count(&server, /*expected_count*/ 1).await?;
assert_eq!(turn_completed.turn.id, turn.id);
assert_eq!(turn_completed.turn.status, TurnStatus::Completed);
@@ -116,7 +116,7 @@ async fn plan_mode_without_proposed_plan_does_not_emit_plan_item() -> Result<()>
let _turn = start_plan_mode_turn(&mut mcp).await?;
let (_, completed_items, plan_deltas, _) = collect_turn_notifications(&mut mcp).await?;
wait_for_responses_request_count(&server, 1).await?;
wait_for_responses_request_count(&server, /*expected_count*/ 1).await?;
let has_plan_item = completed_items
.iter()
@@ -118,7 +118,7 @@ async fn plugin_install_returns_invalid_request_for_not_available_plugin() -> Re
"sample-plugin",
"./sample-plugin",
Some("NOT_AVAILABLE"),
None,
/*auth_policy*/ None,
)?;
write_plugin_source(repo_root.path(), "sample-plugin", &[])?;
let marketplace_path =
@@ -217,8 +217,8 @@ async fn plugin_install_force_remote_sync_enables_remote_plugin_before_local_ins
"debug",
"sample-plugin",
"./sample-plugin",
None,
None,
/*install_policy*/ None,
/*auth_policy*/ None,
)?;
write_plugin_source(repo_root.path(), "sample-plugin", &[])?;
let marketplace_path =
@@ -286,8 +286,8 @@ async fn plugin_install_tracks_analytics_event() -> Result<()> {
"debug",
"sample-plugin",
"./sample-plugin",
None,
None,
/*install_policy*/ None,
/*auth_policy*/ None,
)?;
write_plugin_source(repo_root.path(), "sample-plugin", &[])?;
let marketplace_path =
@@ -401,8 +401,8 @@ async fn plugin_install_returns_apps_needing_auth() -> Result<()> {
"debug",
"sample-plugin",
"./sample-plugin",
None,
None,
/*install_policy*/ None,
/*auth_policy*/ None,
)?;
write_plugin_source(repo_root.path(), "sample-plugin", &["alpha", "beta"])?;
let marketplace_path =
@@ -481,7 +481,7 @@ async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> {
"debug",
"sample-plugin",
"./sample-plugin",
None,
/*install_policy*/ None,
Some("ON_USE"),
)?;
write_plugin_source(
@@ -542,8 +542,8 @@ async fn plugin_install_makes_bundled_mcp_servers_available_to_followup_requests
"debug",
"sample-plugin",
"./sample-plugin",
None,
None,
/*install_policy*/ None,
/*auth_policy*/ None,
)?;
write_plugin_source(repo_root.path(), "sample-plugin", &[])?;
std::fs::write(
@@ -931,7 +931,8 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> {
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
wait_for_path_exists(&marker_path).await?;
wait_for_remote_plugin_request_count(&server, "/plugins/list", 1).await?;
wait_for_remote_plugin_request_count(&server, "/plugins/list", /*expected_count*/ 1)
.await?;
let request_id = mcp
.send_plugin_list_request(PluginListParams {
cwds: None,
@@ -957,7 +958,8 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> {
.collect::<Vec<_>>(),
vec![("linear@openai-curated".to_string(), true, true)]
);
wait_for_remote_plugin_request_count(&server, "/plugins/list", 1).await?;
wait_for_remote_plugin_request_count(&server, "/plugins/list", /*expected_count*/ 1)
.await?;
}
let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
@@ -969,7 +971,7 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> {
}
tokio::time::sleep(Duration::from_millis(250)).await;
wait_for_remote_plugin_request_count(&server, "/plugins/list", 1).await?;
wait_for_remote_plugin_request_count(&server, "/plugins/list", /*expected_count*/ 1).await?;
Ok(())
}
@@ -1029,7 +1031,7 @@ async fn plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request() ->
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
wait_for_featured_plugin_request_count(&server, 1).await?;
wait_for_featured_plugin_request_count(&server, /*expected_count*/ 1).await?;
let request_id = mcp
.send_plugin_list_request(PluginListParams {
@@ -99,7 +99,7 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
codex_home.path(),
&responses_server.uri(),
realtime_server.uri(),
true,
/*realtime_enabled*/ true,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -137,7 +137,9 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
assert!(started.session_id.is_some());
assert_eq!(started.version, RealtimeConversationVersion::V2);
let startup_context_request = realtime_server.wait_for_request(0, 0).await;
let startup_context_request = realtime_server
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 0)
.await;
assert_eq!(
startup_context_request.body_json()["type"].as_str(),
Some("session.update")
@@ -306,7 +308,7 @@ async fn realtime_conversation_stop_emits_closed_notification() -> Result<()> {
codex_home.path(),
&responses_server.uri(),
realtime_server.uri(),
true,
/*realtime_enabled*/ true,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -378,7 +380,7 @@ async fn realtime_conversation_requires_feature_flag() -> Result<()> {
codex_home.path(),
&responses_server.uri(),
realtime_server.uri(),
false,
/*realtime_enabled*/ false,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
+1 -1
View File
@@ -149,7 +149,7 @@ async fn review_start_exec_approval_item_id_matches_command_execution_item() ->
"rev-parse".to_string(),
"HEAD".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"review-call-1",
)?,
@@ -53,7 +53,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
"2025-01-05T12:00:00Z",
preview,
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let original_path = codex_home
@@ -267,7 +267,7 @@ async fn thread_fork_surfaces_cloud_requirements_load_errors() -> Result<()> {
"2025-01-05T12:00:00Z",
"Saved user message",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let refresh_token_url = format!("{}/oauth/token", server.uri());
@@ -331,7 +331,7 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<()
"2025-01-05T12:00:00Z",
preview,
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
+142 -106
View File
@@ -58,7 +58,16 @@ async fn list_threads(
source_kinds: Option<Vec<ThreadSourceKind>>,
archived: Option<bool>,
) -> Result<ThreadListResponse> {
list_threads_with_sort(mcp, cursor, limit, providers, source_kinds, None, archived).await
list_threads_with_sort(
mcp,
cursor,
limit,
providers,
source_kinds,
/*sort_key*/ None,
archived,
)
.await
}
async fn list_threads_with_sort(
@@ -110,7 +119,7 @@ where
&ts_rfc,
preview,
Some(provider_for_index(i)),
None,
/*git_info*/ None,
)?);
}
Ok(ids)
@@ -172,11 +181,11 @@ async fn thread_list_basic_empty() -> Result<()> {
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert!(data.is_empty());
@@ -256,7 +265,7 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul
let ThreadListResponse { data, .. } = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(vec![
@@ -264,7 +273,7 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul
ThreadSourceKind::Cli,
ThreadSourceKind::VsCode,
]),
None,
/*archived*/ None,
)
.await?;
let listed = data
@@ -323,7 +332,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
"2025-01-02T12:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let _b = create_fake_rollout(
codex_home.path(),
@@ -331,7 +340,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
"2025-01-01T13:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let _c = create_fake_rollout(
codex_home.path(),
@@ -339,7 +348,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
"2025-01-01T12:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = init_mcp(codex_home.path()).await?;
@@ -350,11 +359,11 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
next_cursor: cursor1,
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(2),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert_eq!(data1.len(), 2);
@@ -380,8 +389,8 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> {
Some(cursor1),
Some(2),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert!(data2.len() <= 2);
@@ -413,7 +422,7 @@ async fn thread_list_respects_provider_filter() -> Result<()> {
"2025-01-02T10:00:00Z",
"X",
Some("mock_provider"),
None,
/*git_info*/ None,
)?; // mock_provider
let _b = create_fake_rollout(
codex_home.path(),
@@ -421,7 +430,7 @@ async fn thread_list_respects_provider_filter() -> Result<()> {
"2025-01-02T11:00:00Z",
"X",
Some("other_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = init_mcp(codex_home.path()).await?;
@@ -431,11 +440,11 @@ async fn thread_list_respects_provider_filter() -> Result<()> {
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["other_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert_eq!(data.len(), 1);
@@ -465,7 +474,7 @@ async fn thread_list_respects_cwd_filter() -> Result<()> {
"2025-01-02T10:00:00Z",
"filtered",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let unfiltered_id = create_fake_rollout(
codex_home.path(),
@@ -473,7 +482,7 @@ async fn thread_list_respects_cwd_filter() -> Result<()> {
"2025-01-02T11:00:00Z",
"unfiltered",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let target_cwd = codex_home.path().join("target-cwd");
@@ -535,7 +544,7 @@ sqlite = true
"2025-01-02T10:00:00Z",
"match: needle",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let _non_match = create_fake_rollout(
codex_home.path(),
@@ -543,7 +552,7 @@ sqlite = true
"2025-01-02T11:00:00Z",
"no hit here",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let newer_match = create_fake_rollout(
codex_home.path(),
@@ -551,7 +560,7 @@ sqlite = true
"2025-01-02T12:00:00Z",
"needle suffix",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
// `thread/list` only applies `search_term` on the sqlite path. In this test we
@@ -560,7 +569,9 @@ sqlite = true
let state_db =
codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into())
.await?;
state_db.mark_backfill_complete(None).await?;
state_db
.mark_backfill_complete(/*last_watermark*/ None)
.await?;
let mut mcp = init_mcp(codex_home.path()).await?;
let request_id = mcp
@@ -602,7 +613,7 @@ async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result
"2025-02-01T10:00:00Z",
"CLI",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let exec_id = create_fake_rollout_with_source(
codex_home.path(),
@@ -610,7 +621,7 @@ async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result
"2025-02-01T11:00:00Z",
"Exec",
Some("mock_provider"),
None,
/*git_info*/ None,
CoreSessionSource::Exec,
)?;
@@ -620,11 +631,11 @@ async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(Vec::new()),
None,
/*archived*/ None,
)
.await?;
@@ -648,7 +659,7 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<()
"2025-02-01T10:00:00Z",
"CLI",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let parent_thread_id = ThreadId::from_string(&Uuid::new_v4().to_string())?;
@@ -658,7 +669,7 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<()
"2025-02-01T11:00:00Z",
"SubAgent",
Some("mock_provider"),
None,
/*git_info*/ None,
CoreSessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
depth: 1,
@@ -674,11 +685,11 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<()
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(vec![ThreadSourceKind::SubAgentThreadSpawn]),
None,
/*archived*/ None,
)
.await?;
@@ -704,7 +715,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
"2025-02-02T09:00:00Z",
"Review",
Some("mock_provider"),
None,
/*git_info*/ None,
CoreSessionSource::SubAgent(SubAgentSource::Review),
)?;
let compact_id = create_fake_rollout_with_source(
@@ -713,7 +724,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
"2025-02-02T10:00:00Z",
"Compact",
Some("mock_provider"),
None,
/*git_info*/ None,
CoreSessionSource::SubAgent(SubAgentSource::Compact),
)?;
let spawn_id = create_fake_rollout_with_source(
@@ -722,7 +733,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
"2025-02-02T11:00:00Z",
"Spawn",
Some("mock_provider"),
None,
/*git_info*/ None,
CoreSessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
depth: 1,
@@ -737,7 +748,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
"2025-02-02T12:00:00Z",
"Other",
Some("mock_provider"),
None,
/*git_info*/ None,
CoreSessionSource::SubAgent(SubAgentSource::Other("custom".to_string())),
)?;
@@ -745,11 +756,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
let review = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(vec![ThreadSourceKind::SubAgentReview]),
None,
/*archived*/ None,
)
.await?;
let review_ids: Vec<_> = review
@@ -761,11 +772,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
let compact = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(vec![ThreadSourceKind::SubAgentCompact]),
None,
/*archived*/ None,
)
.await?;
let compact_ids: Vec<_> = compact
@@ -777,11 +788,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
let spawn = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(vec![ThreadSourceKind::SubAgentThreadSpawn]),
None,
/*archived*/ None,
)
.await?;
let spawn_ids: Vec<_> = spawn.data.iter().map(|thread| thread.id.as_str()).collect();
@@ -789,11 +800,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> {
let other = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
Some(vec![ThreadSourceKind::SubAgentOther]),
None,
/*archived*/ None,
)
.await?;
let other_ids: Vec<_> = other.data.iter().map(|thread| thread.id.as_str()).collect();
@@ -812,7 +823,7 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> {
// paging past the first two pages to reach the desired count.
create_fake_rollouts(
codex_home.path(),
24,
/*count*/ 24,
|i| {
if i < 16 {
"skip_provider"
@@ -820,7 +831,16 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> {
"target_provider"
}
},
|i| timestamp_at(2025, 3, 30 - i as u32, 12, 0, 0),
|i| {
timestamp_at(
/*year*/ 2025,
/*month*/ 3,
30 - i as u32,
/*hour*/ 12,
/*minute*/ 0,
/*second*/ 0,
)
},
"Hello",
)?;
@@ -832,11 +852,11 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> {
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(8),
Some(vec!["target_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert_eq!(
@@ -864,12 +884,19 @@ async fn thread_list_enforces_max_limit() -> Result<()> {
create_fake_rollouts(
codex_home.path(),
105,
/*count*/ 105,
|_| "mock_provider",
|i| {
let month = 5 + (i / 28);
let day = (i % 28) + 1;
timestamp_at(2025, month as u32, day as u32, 0, 0, 0)
timestamp_at(
/*year*/ 2025,
month as u32,
day as u32,
/*hour*/ 0,
/*minute*/ 0,
/*second*/ 0,
)
},
"Hello",
)?;
@@ -880,11 +907,11 @@ async fn thread_list_enforces_max_limit() -> Result<()> {
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(200),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert_eq!(
@@ -909,7 +936,7 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<()
// ensure the server exhausts pagination without looping forever.
create_fake_rollouts(
codex_home.path(),
22,
/*count*/ 22,
|i| {
if i < 15 {
"skip_provider"
@@ -917,7 +944,16 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<()
"target_provider"
}
},
|i| timestamp_at(2025, 4, 28 - i as u32, 8, 0, 0),
|i| {
timestamp_at(
/*year*/ 2025,
/*month*/ 4,
28 - i as u32,
/*hour*/ 8,
/*minute*/ 0,
/*second*/ 0,
)
},
"Hello",
)?;
@@ -929,11 +965,11 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<()
data, next_cursor, ..
} = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["target_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert_eq!(
@@ -977,11 +1013,11 @@ async fn thread_list_includes_git_info() -> Result<()> {
let ThreadListResponse { data, .. } = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
let thread = data
@@ -1013,7 +1049,7 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> {
"2025-01-02T12:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_b = create_fake_rollout(
codex_home.path(),
@@ -1021,7 +1057,7 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> {
"2025-01-01T13:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_c = create_fake_rollout(
codex_home.path(),
@@ -1029,19 +1065,19 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> {
"2025-01-01T12:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = init_mcp(codex_home.path()).await?;
let ThreadListResponse { data, .. } = list_threads_with_sort(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
None,
None,
/*source_kinds*/ None,
/*sort_key*/ None,
/*archived*/ None,
)
.await?;
@@ -1062,7 +1098,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> {
"2025-01-01T10:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_mid = create_fake_rollout(
codex_home.path(),
@@ -1070,7 +1106,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> {
"2025-01-01T11:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_new = create_fake_rollout(
codex_home.path(),
@@ -1078,7 +1114,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> {
"2025-01-01T12:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
set_rollout_mtime(
@@ -1098,12 +1134,12 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> {
let ThreadListResponse { data, .. } = list_threads_with_sort(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
/*source_kinds*/ None,
Some(ThreadSortKey::UpdatedAt),
None,
/*archived*/ None,
)
.await?;
@@ -1124,7 +1160,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> {
"2025-02-01T10:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_b = create_fake_rollout(
codex_home.path(),
@@ -1132,7 +1168,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> {
"2025-02-01T11:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_c = create_fake_rollout(
codex_home.path(),
@@ -1140,7 +1176,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> {
"2025-02-01T12:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
set_rollout_mtime(
@@ -1164,12 +1200,12 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> {
..
} = list_threads_with_sort(
&mut mcp,
None,
/*cursor*/ None,
Some(2),
Some(vec!["mock_provider".to_string()]),
None,
/*source_kinds*/ None,
Some(ThreadSortKey::UpdatedAt),
None,
/*archived*/ None,
)
.await?;
let ids_page1: Vec<_> = page1.iter().map(|thread| thread.id.as_str()).collect();
@@ -1185,9 +1221,9 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> {
Some(cursor1),
Some(2),
Some(vec!["mock_provider".to_string()]),
None,
/*source_kinds*/ None,
Some(ThreadSortKey::UpdatedAt),
None,
/*archived*/ None,
)
.await?;
let ids_page2: Vec<_> = page2.iter().map(|thread| thread.id.as_str()).collect();
@@ -1208,7 +1244,7 @@ async fn thread_list_created_at_tie_breaks_by_uuid() -> Result<()> {
"2025-02-01T10:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_b = create_fake_rollout(
codex_home.path(),
@@ -1216,18 +1252,18 @@ async fn thread_list_created_at_tie_breaks_by_uuid() -> Result<()> {
"2025-02-01T10:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = init_mcp(codex_home.path()).await?;
let ThreadListResponse { data, .. } = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
@@ -1251,7 +1287,7 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> {
"2025-02-01T10:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let id_b = create_fake_rollout(
codex_home.path(),
@@ -1259,7 +1295,7 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> {
"2025-02-01T11:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let updated_at = "2025-02-03T00:00:00Z";
@@ -1276,12 +1312,12 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> {
let ThreadListResponse { data, .. } = list_threads_with_sort(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
/*source_kinds*/ None,
Some(ThreadSortKey::UpdatedAt),
None,
/*archived*/ None,
)
.await?;
@@ -1305,7 +1341,7 @@ async fn thread_list_updated_at_uses_mtime() -> Result<()> {
"2025-02-01T10:00:00Z",
"Hello",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
set_rollout_mtime(
@@ -1317,12 +1353,12 @@ async fn thread_list_updated_at_uses_mtime() -> Result<()> {
let ThreadListResponse { data, .. } = list_threads_with_sort(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
/*source_kinds*/ None,
Some(ThreadSortKey::UpdatedAt),
None,
/*archived*/ None,
)
.await?;
@@ -1351,7 +1387,7 @@ async fn thread_list_archived_filter() -> Result<()> {
"2025-03-01T10:00:00Z",
"Active",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let archived_id = create_fake_rollout(
codex_home.path(),
@@ -1359,7 +1395,7 @@ async fn thread_list_archived_filter() -> Result<()> {
"2025-03-01T09:00:00Z",
"Archived",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let archived_dir = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR);
@@ -1376,11 +1412,11 @@ async fn thread_list_archived_filter() -> Result<()> {
let ThreadListResponse { data, .. } = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
None,
/*source_kinds*/ None,
/*archived*/ None,
)
.await?;
assert_eq!(data.len(), 1);
@@ -1388,10 +1424,10 @@ async fn thread_list_archived_filter() -> Result<()> {
let ThreadListResponse { data, .. } = list_threads(
&mut mcp,
None,
/*cursor*/ None,
Some(10),
Some(vec!["mock_provider".to_string()]),
None,
/*source_kinds*/ None,
Some(true),
)
.await?;
@@ -184,7 +184,7 @@ async fn thread_metadata_update_repairs_missing_sqlite_row_for_stored_thread() -
"2025-01-05T12:00:00Z",
preview,
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -237,7 +237,7 @@ async fn thread_metadata_update_repairs_loaded_thread_without_resetting_summary(
"2025-01-06T08:30:00Z",
preview,
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let thread_uuid = ThreadId::from_string(&thread_id)?;
let rollout_path = rollout_path(codex_home.path(), "2025-01-06T08-30-00", &thread_id);
@@ -245,10 +245,10 @@ async fn thread_metadata_update_repairs_loaded_thread_without_resetting_summary(
Some(&state_db),
rollout_path.as_path(),
"mock_provider",
None,
/*builder*/ None,
&[],
None,
None,
/*archived_only*/ None,
/*new_thread_memory_mode*/ None,
)
.await;
@@ -317,7 +317,7 @@ async fn thread_metadata_update_repairs_missing_sqlite_row_for_archived_thread()
"2025-01-06T08:30:00Z",
preview,
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let archived_dir = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR);
@@ -430,7 +430,9 @@ async fn thread_metadata_update_can_clear_stored_git_fields() -> Result<()> {
async fn init_state_db(codex_home: &Path) -> Result<Arc<StateRuntime>> {
let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?;
state_db.mark_backfill_complete(None).await?;
state_db
.mark_backfill_complete(/*last_watermark*/ None)
.await?;
Ok(state_db)
}
@@ -43,14 +43,14 @@ async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> {
send_request(
&mut ws1,
"thread/resume",
10,
/*id*/ 10,
Some(serde_json::to_value(ThreadResumeParams {
thread_id: conversation_id.clone(),
..Default::default()
})?),
)
.await?;
let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, 10).await?;
let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, /*id*/ 10).await?;
let resume: ThreadResumeResponse = to_response::<ThreadResumeResponse>(resume_resp)?;
assert_eq!(resume.thread.id, conversation_id);
@@ -58,15 +58,19 @@ async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> {
send_request(
&mut ws1,
"thread/name/set",
11,
/*id*/ 11,
Some(serde_json::to_value(ThreadSetNameParams {
thread_id: conversation_id.clone(),
name: renamed.to_string(),
})?),
)
.await?;
let (rename_resp, ws1_notification) =
read_response_and_notification_for_method(&mut ws1, 11, "thread/name/updated").await?;
let (rename_resp, ws1_notification) = read_response_and_notification_for_method(
&mut ws1,
/*id*/ 11,
"thread/name/updated",
)
.await?;
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
@@ -105,15 +109,19 @@ async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> {
send_request(
&mut ws1,
"thread/name/set",
20,
/*id*/ 20,
Some(serde_json::to_value(ThreadSetNameParams {
thread_id: conversation_id.clone(),
name: renamed.to_string(),
})?),
)
.await?;
let (rename_resp, ws1_notification) =
read_response_and_notification_for_method(&mut ws1, 20, "thread/name/updated").await?;
let (rename_resp, ws1_notification) = read_response_and_notification_for_method(
&mut ws1,
/*id*/ 20,
"thread/name/updated",
)
.await?;
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
@@ -135,11 +143,11 @@ async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> {
}
async fn initialize_both_clients(ws1: &mut WsClient, ws2: &mut WsClient) -> Result<()> {
send_initialize_request(ws1, 1, "ws_client_one").await?;
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, 1)).await??;
send_initialize_request(ws1, /*id*/ 1, "ws_client_one").await?;
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, /*id*/ 1)).await??;
send_initialize_request(ws2, 2, "ws_client_two").await?;
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, 2)).await??;
send_initialize_request(ws2, /*id*/ 2, "ws_client_two").await?;
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, /*id*/ 2)).await??;
Ok(())
}
@@ -151,7 +159,7 @@ fn create_rollout(codex_home: &std::path::Path, filename_ts: &str) -> Result<Str
"Saved user message",
Vec::new(),
Some("mock_provider"),
None,
/*git_info*/ None,
)
}
@@ -57,7 +57,7 @@ async fn thread_read_returns_summary_without_turns() -> Result<()> {
.map(|elem| serde_json::to_value(elem).expect("serialize text element"))
.collect(),
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -112,7 +112,7 @@ async fn thread_read_can_include_turns() -> Result<()> {
.map(|elem| serde_json::to_value(elem).expect("serialize text element"))
.collect(),
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -215,7 +215,7 @@ async fn thread_name_set_is_reflected_in_read_list_and_resume() -> Result<()> {
preview,
vec![],
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -171,7 +171,7 @@ async fn thread_resume_returns_rollout_history() -> Result<()> {
.map(|elem| serde_json::to_value(elem).expect("serialize text element"))
.collect(),
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -368,7 +368,9 @@ stream_max_retries = 0
)?;
let state_db =
StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?;
state_db.mark_backfill_complete(None).await?;
state_db
.mark_backfill_complete(/*last_watermark*/ None)
.await?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
@@ -429,7 +431,7 @@ async fn thread_resume_and_read_interrupt_incomplete_rollout_turn_when_thread_is
"Saved user message",
Vec::new(),
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let rollout_file_path = rollout_path(codex_home.path(), filename_ts, &conversation_id);
let persisted_rollout = std::fs::read_to_string(&rollout_file_path)?;
@@ -1022,7 +1024,7 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call-1",
)?,
@@ -1143,7 +1145,7 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R
primary.read_stream_until_notification_message("turn/completed"),
)
.await??;
wait_for_responses_request_count(&server, 3).await?;
wait_for_responses_request_count(&server, /*expected_count*/ 3).await?;
Ok(())
}
@@ -1309,7 +1311,7 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result<
primary.read_stream_until_notification_message("turn/completed"),
)
.await??;
wait_for_responses_request_count(&server, 3).await?;
wait_for_responses_request_count(&server, /*expected_count*/ 3).await?;
Ok(())
}
@@ -1465,7 +1467,7 @@ async fn thread_resume_surfaces_cloud_requirements_load_errors() -> Result<()> {
"Saved user message",
Vec::new(),
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let refresh_token_url = format!("{}/oauth/token", server.uri());
let mut mcp = McpProcess::new_with_env(
@@ -1933,7 +1935,7 @@ fn setup_rollout_fixture(codex_home: &Path, server_uri: &str) -> Result<RolloutF
preview,
Vec::new(),
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let rollout_file_path = rollout_path(codex_home, filename_ts, &conversation_id);
set_rollout_mtime(rollout_file_path.as_path(), expected_updated_at_rfc3339)?;
@@ -81,7 +81,7 @@ async fn thread_shell_command_runs_as_standalone_turn_and_persists_history() ->
.await??;
let _: ThreadShellCommandResponse = to_response::<ThreadShellCommandResponse>(shell_resp)?;
let started = wait_for_command_execution_started(&mut mcp, None).await?;
let started = wait_for_command_execution_started(&mut mcp, /*expected_id*/ None).await?;
let ThreadItem::CommandExecution {
id, source, status, ..
} = &started.item
@@ -167,7 +167,7 @@ async fn thread_shell_command_uses_existing_active_turn() -> Result<()> {
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call-approve",
)?,
@@ -345,7 +345,7 @@ async fn wait_for_command_execution_started_by_source(
expected_source: CommandExecutionSource,
) -> Result<ItemStartedNotification> {
loop {
let started = wait_for_command_execution_started(mcp, None).await?;
let started = wait_for_command_execution_started(mcp, /*expected_id*/ None).await?;
let ThreadItem::CommandExecution { source, .. } = &started.item else {
continue;
};
@@ -219,7 +219,7 @@ async fn thread_unsubscribe_during_turn_interrupts_turn_and_emits_thread_closed(
wait_for_responses_request_count_to_stabilize(
&server,
1,
/*expected_count*/ 1,
std::time::Duration::from_millis(200),
)
.await?;
@@ -888,7 +888,7 @@ async fn turn_start_uses_migrated_pragmatic_personality_without_override_v2() ->
"2025-01-01T00:00:00Z",
"history user message",
Some("mock_provider"),
None,
/*git_info*/ None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -1031,7 +1031,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call1",
)?,
@@ -1042,7 +1042,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call2",
)?,
@@ -1186,7 +1186,7 @@ async fn turn_start_exec_approval_decline_v2() -> Result<()> {
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call-decline",
)?,
@@ -1331,14 +1331,14 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
let responses = vec![
create_shell_command_sse_response(
vec!["echo".to_string(), "first".to_string(), "turn".to_string()],
None,
/*workdir*/ None,
Some(5000),
"call-first",
)?,
create_final_assistant_message_sse_response("done first")?,
create_shell_command_sse_response(
vec!["echo".to_string(), "second".to_string(), "turn".to_string()],
None,
/*workdir*/ None,
Some(5000),
"call-second",
)?,
@@ -71,7 +71,7 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> {
format!("while [ ! -f '{release_marker_escaped}' ]; do sleep 0.01; done");
let response = create_shell_command_sse_response(
vec!["/bin/sh".to_string(), "-c".to_string(), wait_for_interrupt],
None,
/*workdir*/ None,
Some(5000),
"call-zsh-fork",
)?;
@@ -197,7 +197,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> {
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call-zsh-fork-decline",
)?,
@@ -332,7 +332,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> {
"-c".to_string(),
"print(42)".to_string(),
],
None,
/*workdir*/ None,
Some(5000),
"call-zsh-fork-cancel",
)?];
@@ -805,7 +805,7 @@ fn find_test_zsh_path() -> Result<Option<std::path::PathBuf>> {
);
return Ok(None);
}
match core_test_support::fetch_dotslash_file(&dotslash_zsh, None) {
match core_test_support::fetch_dotslash_file(&dotslash_zsh, /*dotslash_cache*/ None) {
Ok(path) => return Ok(Some(path)),
Err(error) => {
eprintln!("failed to fetch vendored zsh via dotslash: {error:#}");
@@ -26,7 +26,7 @@ async fn windows_sandbox_setup_start_emits_completion_notification() -> Result<(
codex_home.path(),
&server.uri(),
&BTreeMap::new(),
500_000,
/*auto_compact_limit*/ 500_000,
Some(false),
"mock_provider",
"compact prompt",
+8 -5
View File
@@ -524,14 +524,14 @@ mod tests {
#[test]
fn test_heredoc() {
assert_match(&heredoc_script(""), None);
assert_match(&heredoc_script(""), /*expected_workdir*/ None);
}
#[test]
fn test_heredoc_non_login_shell() {
let script = heredoc_script("");
let args = strs_to_strings(&["bash", "-c", &script]);
assert_match_args(args, None);
assert_match_args(args, /*expected_workdir*/ None);
}
#[test]
@@ -565,17 +565,20 @@ PATCH"#,
#[test]
fn test_powershell_heredoc() {
let script = heredoc_script("");
assert_match_args(args_powershell(&script), None);
assert_match_args(args_powershell(&script), /*expected_workdir*/ None);
}
#[test]
fn test_powershell_heredoc_no_profile() {
let script = heredoc_script("");
assert_match_args(args_powershell_no_profile(&script), None);
assert_match_args(
args_powershell_no_profile(&script),
/*expected_workdir*/ None,
);
}
#[test]
fn test_pwsh_heredoc() {
let script = heredoc_script("");
assert_match_args(args_pwsh(&script), None);
assert_match_args(args_pwsh(&script), /*expected_workdir*/ None);
}
#[test]
+24 -8
View File
@@ -672,7 +672,7 @@ fn test_parse_patch_lenient() {
#[test]
fn test_parse_one_hunk() {
assert_eq!(
parse_one_hunk(&["bad"], 234),
parse_one_hunk(&["bad"], /*line_number*/ 234),
Err(InvalidHunkError {
message: "'bad' is not a valid hunk header. \
Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'".to_string(),
@@ -685,7 +685,11 @@ fn test_parse_one_hunk() {
#[test]
fn test_update_file_chunk() {
assert_eq!(
parse_update_file_chunk(&["bad"], 123, false),
parse_update_file_chunk(
&["bad"],
/*line_number*/ 123,
/*allow_missing_context*/ false
),
Err(InvalidHunkError {
message: "Expected update hunk to start with a @@ context marker, got: 'bad'"
.to_string(),
@@ -693,14 +697,18 @@ fn test_update_file_chunk() {
})
);
assert_eq!(
parse_update_file_chunk(&["@@"], 123, false),
parse_update_file_chunk(
&["@@"],
/*line_number*/ 123,
/*allow_missing_context*/ false
),
Err(InvalidHunkError {
message: "Update hunk does not contain any lines".to_string(),
line_number: 124
})
);
assert_eq!(
parse_update_file_chunk(&["@@", "bad"], 123, false),
parse_update_file_chunk(&["@@", "bad"], /*line_number*/ 123, /*allow_missing_context*/ false),
Err(InvalidHunkError {
message: "Unexpected line found in update hunk: 'bad'. \
Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)".to_string(),
@@ -708,7 +716,11 @@ fn test_update_file_chunk() {
})
);
assert_eq!(
parse_update_file_chunk(&["@@", "*** End of File"], 123, false),
parse_update_file_chunk(
&["@@", "*** End of File"],
/*line_number*/ 123,
/*allow_missing_context*/ false
),
Err(InvalidHunkError {
message: "Update hunk does not contain any lines".to_string(),
line_number: 124
@@ -725,8 +737,8 @@ fn test_update_file_chunk() {
" context2",
"*** End Patch",
],
123,
false
/*line_number*/ 123,
/*allow_missing_context*/ false
),
Ok((
(UpdateFileChunk {
@@ -749,7 +761,11 @@ fn test_update_file_chunk() {
))
);
assert_eq!(
parse_update_file_chunk(&["@@", "+line", "*** End of File"], 123, false),
parse_update_file_chunk(
&["@@", "+line", "*** End of File"],
/*line_number*/ 123,
/*allow_missing_context*/ false
),
Ok((
(UpdateFileChunk {
change_context: None,
+16 -4
View File
@@ -122,7 +122,10 @@ mod tests {
fn test_exact_match_finds_sequence() {
let lines = to_vec(&["foo", "bar", "baz"]);
let pattern = to_vec(&["bar", "baz"]);
assert_eq!(seek_sequence(&lines, &pattern, 0, false), Some(1));
assert_eq!(
seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false),
Some(1)
);
}
#[test]
@@ -130,7 +133,10 @@ mod tests {
let lines = to_vec(&["foo ", "bar\t\t"]);
// Pattern omits trailing whitespace.
let pattern = to_vec(&["foo", "bar"]);
assert_eq!(seek_sequence(&lines, &pattern, 0, false), Some(0));
assert_eq!(
seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false),
Some(0)
);
}
#[test]
@@ -138,7 +144,10 @@ mod tests {
let lines = to_vec(&[" foo ", " bar\t"]);
// Pattern omits any additional whitespace.
let pattern = to_vec(&["foo", "bar"]);
assert_eq!(seek_sequence(&lines, &pattern, 0, false), Some(0));
assert_eq!(
seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false),
Some(0)
);
}
#[test]
@@ -146,6 +155,9 @@ mod tests {
let lines = to_vec(&["just one line"]);
let pattern = to_vec(&["too", "many", "lines"]);
// Should not panic – must return None when pattern cannot possibly fit.
assert_eq!(seek_sequence(&lines, &pattern, 0, false), None);
assert_eq!(
seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false),
None
);
}
}
+11 -5
View File
@@ -245,9 +245,9 @@ mod tests {
let merged = merge_connectors_with_accessible(
vec![app("alpha")],
vec![app("alpha"), app("beta")],
true,
/*all_connectors_loaded*/ true,
);
assert_eq!(merged, vec![merged_app("alpha", true)]);
assert_eq!(merged, vec![merged_app("alpha", /*is_accessible*/ true)]);
}
#[test]
@@ -255,11 +255,14 @@ mod tests {
let merged = merge_connectors_with_accessible(
vec![app("alpha")],
vec![app("alpha"), app("beta")],
false,
/*all_connectors_loaded*/ false,
);
assert_eq!(
merged,
vec![merged_app("alpha", true), merged_app("beta", true)]
vec![
merged_app("alpha", /*is_accessible*/ true),
merged_app("beta", /*is_accessible*/ true)
]
);
}
@@ -272,7 +275,10 @@ mod tests {
AppConnectorId("gmail".to_string()),
],
);
assert_eq!(connectors, vec![app("alpha"), merged_app("gmail", false)]);
assert_eq!(
connectors,
vec![app("alpha"), merged_app("gmail", /*is_accessible*/ false)]
);
}
#[test]
+5 -5
View File
@@ -498,7 +498,7 @@ mod tests {
let legacy_config = build_debug_sandbox_config(
Vec::new(),
ConfigOverrides {
sandbox_mode: Some(create_sandbox_mode(false)),
sandbox_mode: Some(create_sandbox_mode(/*full_auto*/ false)),
..Default::default()
},
Some(codex_home_path.clone()),
@@ -507,8 +507,8 @@ mod tests {
let config = load_debug_sandbox_config_with_codex_home(
Vec::new(),
None,
false,
/*codex_linux_sandbox_exe*/ None,
/*full_auto*/ false,
Some(codex_home_path),
)
.await?;
@@ -541,8 +541,8 @@ mod tests {
let err = load_debug_sandbox_config_with_codex_home(
Vec::new(),
None,
true,
/*codex_linux_sandbox_exe*/ None,
/*full_auto*/ true,
Some(codex_home.path().to_path_buf()),
)
.await
+25 -11
View File
@@ -1513,14 +1513,17 @@ mod tests {
update_action: None,
exit_reason: ExitReason::UserRequested,
};
let lines = format_exit_messages(exit_info, false);
let lines = format_exit_messages(exit_info, /*color_enabled*/ false);
assert!(lines.is_empty());
}
#[test]
fn format_exit_messages_includes_resume_hint_without_color() {
let exit_info = sample_exit_info(Some("123e4567-e89b-12d3-a456-426614174000"), None);
let lines = format_exit_messages(exit_info, false);
let exit_info = sample_exit_info(
Some("123e4567-e89b-12d3-a456-426614174000"),
/*thread_name*/ None,
);
let lines = format_exit_messages(exit_info, /*color_enabled*/ false);
assert_eq!(
lines,
vec![
@@ -1533,8 +1536,11 @@ mod tests {
#[test]
fn format_exit_messages_applies_color_when_enabled() {
let exit_info = sample_exit_info(Some("123e4567-e89b-12d3-a456-426614174000"), None);
let lines = format_exit_messages(exit_info, true);
let exit_info = sample_exit_info(
Some("123e4567-e89b-12d3-a456-426614174000"),
/*thread_name*/ None,
);
let lines = format_exit_messages(exit_info, /*color_enabled*/ true);
assert_eq!(lines.len(), 2);
assert!(lines[1].contains("\u{1b}[36m"));
}
@@ -1545,7 +1551,7 @@ mod tests {
Some("123e4567-e89b-12d3-a456-426614174000"),
Some("my-thread"),
);
let lines = format_exit_messages(exit_info, false);
let lines = format_exit_messages(exit_info, /*color_enabled*/ false);
assert_eq!(
lines,
vec![
@@ -1771,8 +1777,12 @@ mod tests {
#[test]
fn reject_remote_mode_for_non_interactive_subcommands() {
let err = reject_remote_mode_for_subcommand(Some("127.0.0.1:4500"), None, "exec")
.expect_err("non-interactive subcommands should reject --remote");
let err = reject_remote_mode_for_subcommand(
Some("127.0.0.1:4500"),
/*remote_auth_token_env*/ None,
"exec",
)
.expect_err("non-interactive subcommands should reject --remote");
assert!(
err.to_string()
.contains("only supported for interactive TUI commands")
@@ -1781,8 +1791,12 @@ mod tests {
#[test]
fn reject_remote_auth_token_env_for_non_interactive_subcommands() {
let err = reject_remote_mode_for_subcommand(None, Some("CODEX_REMOTE_AUTH_TOKEN"), "exec")
.expect_err("non-interactive subcommands should reject --remote-auth-token-env");
let err = reject_remote_mode_for_subcommand(
/*remote*/ None,
Some("CODEX_REMOTE_AUTH_TOKEN"),
"exec",
)
.expect_err("non-interactive subcommands should reject --remote-auth-token-env");
assert!(
err.to_string()
.contains("only supported for interactive TUI commands")
@@ -1796,7 +1810,7 @@ mod tests {
out_dir: PathBuf::from("/tmp/out"),
});
let err = reject_remote_mode_for_app_server_subcommand(
None,
/*remote*/ None,
Some("CODEX_REMOTE_AUTH_TOKEN"),
Some(&subcommand),
)
+18 -10
View File
@@ -848,7 +848,7 @@ mod tests {
write_auth_json(tmp.path(), auth_json).expect("write auth");
Arc::new(AuthManager::new(
tmp.path().to_path_buf(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
))
}
@@ -872,7 +872,7 @@ mod tests {
.expect("write auth");
Arc::new(AuthManager::new(
tmp.path().to_path_buf(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
))
}
@@ -909,7 +909,7 @@ mod tests {
access_token,
refresh_token,
last_refresh,
None,
/*auth_mode*/ None,
)
}
@@ -980,7 +980,7 @@ mod tests {
ManagedAuthContext {
manager: Arc::new(AuthManager::new(
home.path().to_path_buf(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
)),
_home: home,
@@ -1225,7 +1225,7 @@ mod tests {
#[tokio::test]
async fn fetch_cloud_requirements_handles_missing_contents() {
let result = parse_for_fetch(None);
let result = parse_for_fetch(/*contents*/ None);
assert!(result.is_none());
}
@@ -1369,7 +1369,7 @@ enabled = false
.expect("write initial auth");
let auth_manager = Arc::new(AuthManager::new(
auth_home.path().to_path_buf(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
));
@@ -1438,7 +1438,7 @@ enabled = false
.expect("write initial auth");
let auth_manager = Arc::new(AuthManager::new(
auth_home.path().to_path_buf(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
));
@@ -1565,7 +1565,7 @@ enabled = false
.expect("write auth");
let auth_manager = Arc::new(AuthManager::new(
auth_home.path().to_path_buf(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
));
@@ -1657,7 +1657,11 @@ enabled = false
async fn fetch_cloud_requirements_writes_cache_when_identity_is_incomplete() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan_and_identity("business", None, Some("account-12345")),
auth_manager_with_plan_and_identity(
"business",
/*chatgpt_user_id*/ None,
Some("account-12345"),
),
Arc::new(StaticFetcher {
contents: Some("allowed_approval_policies = [\"never\"]".to_string()),
}),
@@ -1709,7 +1713,11 @@ enabled = false
"allowed_approval_policies = [\"on-request\"]".to_string(),
))]));
let service = CloudRequirementsService::new(
auth_manager_with_plan_and_identity("business", None, Some("account-12345")),
auth_manager_with_plan_and_identity(
"business",
/*chatgpt_user_id*/ None,
Some("account-12345"),
),
fetcher.clone(),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
+2 -2
View File
@@ -408,7 +408,7 @@ mod tests {
&self,
id: TaskId,
) -> codex_cloud_tasks_client::Result<TaskSummary> {
self.list_tasks(None, None, None)
self.list_tasks(/*env*/ None, /*limit*/ None, /*cursor*/ None)
.await?
.tasks
.into_iter()
@@ -497,7 +497,7 @@ mod tests {
let backend = FakeBackend { by_env };
// Act + Assert
let root = load_tasks(&backend, None).await.unwrap();
let root = load_tasks(&backend, /*env*/ None).await.unwrap();
assert_eq!(root.len(), 2);
assert_eq!(root[0].title, "root-1");
+18 -9
View File
@@ -2174,7 +2174,7 @@ mod tests {
async fn branch_override_is_used_when_provided() {
let git_ref = resolve_git_ref_with_git_info(
Some(&"feature/override".to_string()),
&StubGitInfo::new(None, None),
&StubGitInfo::new(/*default_branch*/ None, /*current_branch*/ None),
)
.await;
@@ -2185,7 +2185,7 @@ mod tests {
async fn trims_override_whitespace() {
let git_ref = resolve_git_ref_with_git_info(
Some(&" feature/spaces ".to_string()),
&StubGitInfo::new(None, None),
&StubGitInfo::new(/*default_branch*/ None, /*current_branch*/ None),
)
.await;
@@ -2195,7 +2195,7 @@ mod tests {
#[tokio::test]
async fn prefers_current_branch_when_available() {
let git_ref = resolve_git_ref_with_git_info(
None,
/*branch_override*/ None,
&StubGitInfo::new(
Some("default-main".to_string()),
Some("feature/current".to_string()),
@@ -2209,8 +2209,8 @@ mod tests {
#[tokio::test]
async fn falls_back_to_current_branch_when_default_is_missing() {
let git_ref = resolve_git_ref_with_git_info(
None,
&StubGitInfo::new(None, Some("develop".to_string())),
/*branch_override*/ None,
&StubGitInfo::new(/*default_branch*/ None, Some("develop".to_string())),
)
.await;
@@ -2219,7 +2219,11 @@ mod tests {
#[tokio::test]
async fn falls_back_to_main_when_no_git_info_is_available() {
let git_ref = resolve_git_ref_with_git_info(None, &StubGitInfo::new(None, None)).await;
let git_ref = resolve_git_ref_with_git_info(
/*branch_override*/ None,
&StubGitInfo::new(/*default_branch*/ None, /*current_branch*/ None),
)
.await;
assert_eq!(git_ref, "main");
}
@@ -2242,7 +2246,7 @@ mod tests {
is_review: false,
attempt_total: None,
};
let lines = format_task_status_lines(&task, now, false);
let lines = format_task_status_lines(&task, now, /*colorize*/ false);
assert_eq!(
lines,
vec![
@@ -2267,7 +2271,7 @@ mod tests {
is_review: false,
attempt_total: Some(1),
};
let lines = format_task_status_lines(&task, now, false);
let lines = format_task_status_lines(&task, now, /*colorize*/ false);
assert_eq!(
lines,
vec![
@@ -2309,7 +2313,12 @@ mod tests {
attempt_total: Some(1),
},
];
let lines = format_task_list_lines(&tasks, "https://chatgpt.com/backend-api", now, false);
let lines = format_task_list_lines(
&tasks,
"https://chatgpt.com/backend-api",
now,
/*colorize*/ false,
);
assert_eq!(
lines,
vec![
+24 -12
View File
@@ -5,23 +5,35 @@ use codex_cloud_tasks_client::MockClient;
async fn mock_backend_varies_by_env() {
let client = MockClient;
let root = CloudBackend::list_tasks(&client, None, None, None)
.await
.unwrap()
.tasks;
let root = CloudBackend::list_tasks(
&client, /*env*/ None, /*limit*/ None, /*cursor*/ None,
)
.await
.unwrap()
.tasks;
assert!(root.iter().any(|t| t.title.contains("Update README")));
let a = CloudBackend::list_tasks(&client, Some("env-A"), None, None)
.await
.unwrap()
.tasks;
let a = CloudBackend::list_tasks(
&client,
Some("env-A"),
/*limit*/ None,
/*cursor*/ None,
)
.await
.unwrap()
.tasks;
assert_eq!(a.len(), 1);
assert_eq!(a[0].title, "A: First");
let b = CloudBackend::list_tasks(&client, Some("env-B"), None, None)
.await
.unwrap()
.tasks;
let b = CloudBackend::list_tasks(
&client,
Some("env-B"),
/*limit*/ None,
/*cursor*/ None,
)
.await
.unwrap()
.tasks;
assert_eq!(b.len(), 2);
assert!(b[0].title.starts_with("B: "));
}
+4 -2
View File
@@ -548,8 +548,10 @@ mod tests {
#[test]
fn code_mode_only_description_includes_nested_tools() {
let description =
build_exec_tool_description(&[("foo".to_string(), "bar".to_string())], true);
let description = build_exec_tool_description(
&[("foo".to_string(), "bar".to_string())],
/*code_mode_only*/ true,
);
assert!(description.contains("### `foo` (`foo`)"));
}
}
+1 -1
View File
@@ -631,7 +631,7 @@ text(JSON.stringify(returnsUndefined));
event_rx,
control_rx,
initial_response_tx,
60_000,
/*initial_yield_time_ms*/ 60_000,
));
event_tx.send(RuntimeEvent::Started).unwrap();
@@ -967,8 +967,8 @@ mod tests {
fn websocket_url_from_http_base_defaults_to_ws_path() {
let url = websocket_url_from_api_url(
"http://127.0.0.1:8011",
None,
None,
/*query_params*/ None,
/*model*/ None,
RealtimeEventParser::V1,
RealtimeSessionMode::Conversational,
)
@@ -983,7 +983,7 @@ mod tests {
fn websocket_url_from_ws_base_defaults_to_ws_path() {
let url = websocket_url_from_api_url(
"wss://example.com",
None,
/*query_params*/ None,
Some("realtime-test-model"),
RealtimeEventParser::V1,
RealtimeSessionMode::Conversational,
@@ -999,7 +999,7 @@ mod tests {
fn websocket_url_from_v1_base_appends_realtime_path() {
let url = websocket_url_from_api_url(
"https://api.openai.com/v1",
None,
/*query_params*/ None,
Some("snapshot"),
RealtimeEventParser::V1,
RealtimeSessionMode::Conversational,
@@ -1015,7 +1015,7 @@ mod tests {
fn websocket_url_from_nested_v1_base_appends_realtime_path() {
let url = websocket_url_from_api_url(
"https://example.com/openai/v1",
None,
/*query_params*/ None,
Some("snapshot"),
RealtimeEventParser::V1,
RealtimeSessionMode::Conversational,
@@ -1050,8 +1050,8 @@ mod tests {
fn websocket_url_v1_ignores_transcription_mode() {
let url = websocket_url_from_api_url(
"https://example.com",
None,
None,
/*query_params*/ None,
/*model*/ None,
RealtimeEventParser::V1,
RealtimeSessionMode::Transcription,
)
@@ -1085,8 +1085,8 @@ mod tests {
fn websocket_url_omits_intent_for_realtime_v2_transcription_mode() {
let url = websocket_url_from_api_url(
"https://example.com",
None,
None,
/*query_params*/ None,
/*model*/ None,
RealtimeEventParser::RealtimeV2,
RealtimeSessionMode::Transcription,
)
+1 -1
View File
@@ -277,7 +277,7 @@ mod tests {
HeaderValue::from_static("1704069000"),
);
let snapshot = parse_rate_limit_for_limit(&headers, None).expect("snapshot");
let snapshot = parse_rate_limit_for_limit(&headers, /*limit_id*/ None).expect("snapshot");
assert_eq!(snapshot.limit_id.as_deref(), Some("codex"));
assert_eq!(snapshot.limit_name, None);
let primary = snapshot.primary.expect("primary");
+24 -4
View File
@@ -515,7 +515,12 @@ mod tests {
let stream =
ReaderStream::new(reader).map_err(|err| TransportError::Network(err.to_string()));
let (tx, mut rx) = mpsc::channel::<Result<ResponseEvent, ApiError>>(16);
tokio::spawn(process_sse(Box::pin(stream), tx, idle_timeout(), None));
tokio::spawn(process_sse(
Box::pin(stream),
tx,
idle_timeout(),
/*telemetry*/ None,
));
let mut events = Vec::new();
while let Some(ev) = rx.recv().await {
@@ -541,7 +546,12 @@ mod tests {
let (tx, mut rx) = mpsc::channel::<Result<ResponseEvent, ApiError>>(8);
let stream = ReaderStream::new(std::io::Cursor::new(body))
.map_err(|err| TransportError::Network(err.to_string()));
tokio::spawn(process_sse(Box::pin(stream), tx, idle_timeout(), None));
tokio::spawn(process_sse(
Box::pin(stream),
tx,
idle_timeout(),
/*telemetry*/ None,
));
let mut out = Vec::new();
while let Some(ev) = rx.recv().await {
@@ -695,7 +705,12 @@ mod tests {
let stream: ByteStream = Box::pin(stream);
let (tx, mut rx) = mpsc::channel::<Result<ResponseEvent, ApiError>>(8);
tokio::spawn(process_sse(stream, tx, idle_timeout(), None));
tokio::spawn(process_sse(
stream,
tx,
idle_timeout(),
/*telemetry*/ None,
));
let events = tokio::time::timeout(Duration::from_millis(1000), async {
let mut events = Vec::new();
@@ -894,7 +909,12 @@ mod tests {
bytes: Box::pin(bytes),
};
let mut stream = spawn_response_stream(stream_response, idle_timeout(), None, None);
let mut stream = spawn_response_stream(
stream_response,
idle_timeout(),
/*telemetry*/ None,
/*turn_state*/ None,
);
let event = stream
.rx_event
.recv()
+12 -2
View File
@@ -203,7 +203,12 @@ async fn responses_client_uses_responses_path() -> Result<()> {
let body = serde_json::json!({ "echo": true });
let _stream = client
.stream(body, HeaderMap::new(), Compression::None, None)
.stream(
body,
HeaderMap::new(),
Compression::None,
/*turn_state*/ None,
)
.await?;
let requests = state.take_stream_requests();
@@ -220,7 +225,12 @@ async fn streaming_client_adds_auth_headers() -> Result<()> {
let body = serde_json::json!({ "model": "gpt-test" });
let _stream = client
.stream(body, HeaderMap::new(), Compression::None, None)
.stream(
body,
HeaderMap::new(),
Compression::None,
/*turn_state*/ None,
)
.await?;
let requests = state.take_stream_requests();
@@ -85,7 +85,7 @@ async fn models_client_hits_models_endpoint() {
availability_nux: None,
apply_patch_tool_type: None,
web_search_tool_type: Default::default(),
truncation_policy: TruncationPolicyConfig::bytes(10_000),
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
supports_parallel_tool_calls: false,
supports_image_detail_original: false,
context_window: Some(272_000),
+1 -1
View File
@@ -125,7 +125,7 @@ async fn responses_stream_parses_items_and_completed_end_to_end() -> Result<()>
serde_json::json!({"echo": true}),
HeaderMap::new(),
Compression::None,
None,
/*turn_state*/ None,
)
.await?;
+15 -12
View File
@@ -188,8 +188,10 @@ mod tests {
#[test]
fn constrained_allow_any_accepts_any_value() {
let mut constrained = Constrained::allow_any(5);
constrained.set(-10).expect("allow any accepts all values");
let mut constrained = Constrained::allow_any(/*initial_value*/ 5);
constrained
.set(/*value*/ -10)
.expect("allow any accepts all values");
assert_eq!(constrained.value(), -10);
}
@@ -201,13 +203,13 @@ mod tests {
#[test]
fn constrained_allow_only_rejects_different_values() {
let mut constrained = Constrained::allow_only(5);
let mut constrained = Constrained::allow_only(/*only_value*/ 5);
constrained
.set(5)
.set(/*value*/ 5)
.expect("allowed value should be accepted");
let err = constrained
.set(6)
.set(/*value*/ 6)
.expect_err("different value should be rejected");
assert_eq!(err, invalid_value("6", "[5]"));
assert_eq!(constrained.value(), 5);
@@ -215,18 +217,19 @@ mod tests {
#[test]
fn constrained_normalizer_applies_on_init_and_set() -> anyhow::Result<()> {
let mut constrained = Constrained::normalized(-1, |value| value.max(0))?;
let mut constrained =
Constrained::normalized(/*initial_value*/ -1, |value| value.max(0))?;
assert_eq!(constrained.value(), 0);
constrained.set(-5)?;
constrained.set(/*value*/ -5)?;
assert_eq!(constrained.value(), 0);
constrained.set(10)?;
constrained.set(/*value*/ 10)?;
assert_eq!(constrained.value(), 10);
Ok(())
}
#[test]
fn constrained_new_rejects_invalid_initial_value() {
let result = Constrained::new(0, |value| {
let result = Constrained::new(/*initial_value*/ 0, |value| {
if *value > 0 {
Ok(())
} else {
@@ -239,7 +242,7 @@ mod tests {
#[test]
fn constrained_set_rejects_invalid_value_and_leaves_previous() {
let mut constrained = Constrained::new(1, |value| {
let mut constrained = Constrained::new(/*initial_value*/ 1, |value| {
if *value > 0 {
Ok(())
} else {
@@ -249,7 +252,7 @@ mod tests {
.expect("initial value should be accepted");
let err = constrained
.set(-5)
.set(/*value*/ -5)
.expect_err("negative values should be rejected");
assert_eq!(err, invalid_value("-5", "positive values"));
assert_eq!(constrained.value(), 1);
@@ -257,7 +260,7 @@ mod tests {
#[test]
fn constrained_can_set_allows_probe_without_setting() {
let constrained = Constrained::new(1, |value| {
let constrained = Constrained::new(/*initial_value*/ 1, |value| {
if *value > 0 {
Ok(())
} else {
+62 -47
View File
@@ -419,7 +419,7 @@ mod tests {
"https://chatgpt.example".to_string(),
Some(format!("account-{id}")),
Some(format!("user-{id}")),
true,
/*is_workspace_account*/ true,
)
}
@@ -444,21 +444,31 @@ mod tests {
let call_counter = Arc::clone(&calls);
let key = cache_key("shared");
let first = list_all_connectors_with_options(key.clone(), false, false, move |_path| {
let call_counter = Arc::clone(&call_counter);
async move {
call_counter.fetch_add(1, Ordering::SeqCst);
Ok(DirectoryListResponse {
apps: vec![app("alpha", "Alpha")],
next_token: None,
})
}
})
let first = list_all_connectors_with_options(
key.clone(),
/*is_workspace_account*/ false,
/*force_refetch*/ false,
move |_path| {
let call_counter = Arc::clone(&call_counter);
async move {
call_counter.fetch_add(1, Ordering::SeqCst);
Ok(DirectoryListResponse {
apps: vec![app("alpha", "Alpha")],
next_token: None,
})
}
},
)
.await?;
let second = list_all_connectors_with_options(key, false, false, move |_path| async move {
anyhow::bail!("cache should have been used");
})
let second = list_all_connectors_with_options(
key,
/*is_workspace_account*/ false,
/*force_refetch*/ false,
move |_path| async move {
anyhow::bail!("cache should have been used");
},
)
.await?;
assert_eq!(calls.load(Ordering::SeqCst), 1);
@@ -472,40 +482,45 @@ mod tests {
let calls = Arc::new(AtomicUsize::new(0));
let call_counter = Arc::clone(&calls);
let connectors = list_all_connectors_with_options(key, true, true, move |path| {
let call_counter = Arc::clone(&call_counter);
async move {
call_counter.fetch_add(1, Ordering::SeqCst);
if path.starts_with("/connectors/directory/list_workspace") {
Ok(DirectoryListResponse {
apps: vec![
DirectoryApp {
description: Some("Merged description".to_string()),
branding: Some(AppBranding {
category: Some("calendar".to_string()),
developer: None,
website: None,
privacy_policy: None,
terms_of_service: None,
is_discoverable_app: true,
}),
..app("alpha", "")
},
DirectoryApp {
visibility: Some("HIDDEN".to_string()),
..app("hidden", "Hidden")
},
],
next_token: None,
})
} else {
Ok(DirectoryListResponse {
apps: vec![app("alpha", " Alpha "), app("beta", "Beta")],
next_token: None,
})
let connectors = list_all_connectors_with_options(
key,
/*is_workspace_account*/ true,
/*force_refetch*/ true,
move |path| {
let call_counter = Arc::clone(&call_counter);
async move {
call_counter.fetch_add(1, Ordering::SeqCst);
if path.starts_with("/connectors/directory/list_workspace") {
Ok(DirectoryListResponse {
apps: vec![
DirectoryApp {
description: Some("Merged description".to_string()),
branding: Some(AppBranding {
category: Some("calendar".to_string()),
developer: None,
website: None,
privacy_policy: None,
terms_of_service: None,
is_discoverable_app: true,
}),
..app("alpha", "")
},
DirectoryApp {
visibility: Some("HIDDEN".to_string()),
..app("hidden", "Hidden")
},
],
next_token: None,
})
} else {
Ok(DirectoryListResponse {
apps: vec![app("alpha", " Alpha "), app("beta", "Beta")],
next_token: None,
})
}
}
}
})
},
)
.await?;
assert_eq!(calls.load(Ordering::SeqCst), 2);
+1 -1
View File
@@ -115,7 +115,7 @@ fn load_skills_for_test(config: &TestConfig) -> SkillLoadOutcome {
super::load_skills_from_roots(super::skill_roots_with_home_dir(
&config.config_layer_stack,
&config.cwd,
None,
/*home_dir*/ None,
Vec::new(),
))
}
+55 -23
View File
@@ -141,7 +141,10 @@ fn new_with_disabled_bundled_skills_removes_stale_cached_system_skills() {
fs::write(stale_system_skill_dir.join("SKILL.md"), "# stale\n")
.expect("write stale system skill");
let _skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), false);
let _skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ false,
);
assert!(
!codex_home.path().join("skills/.system").exists(),
@@ -154,7 +157,10 @@ async fn skills_for_config_reuses_cache_for_same_effective_config() {
let codex_home = tempfile::tempdir().expect("tempdir");
let cwd = tempfile::tempdir().expect("tempdir");
let config_layer_stack = config_stack(&codex_home, "");
let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true);
let skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ true,
);
write_user_skill(&codex_home, "a", "skill-a", "from a");
let outcome1 = skills_for_config_with_stack(&skills_manager, &cwd, &config_layer_stack, &[]);
@@ -185,14 +191,17 @@ async fn skills_for_config_disables_plugin_skills_by_name() {
);
let config_layer_stack = config_stack(
&codex_home,
&name_toggle_config("sample:sample-search", false),
&name_toggle_config("sample:sample-search", /*enabled*/ false),
);
let plugin_skill_root = skill_path
.parent()
.and_then(std::path::Path::parent)
.expect("plugin skill should live under a skills root")
.to_path_buf();
let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true);
let skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ true,
);
let outcome = skills_for_config_with_stack(
&skills_manager,
@@ -223,7 +232,10 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() {
let cwd = tempfile::tempdir().expect("tempdir");
let extra_root = tempfile::tempdir().expect("tempdir");
let config_layer_stack = config_stack(&codex_home, "");
let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true);
let skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ true,
);
let _ = skills_for_config_with_stack(&skills_manager, &cwd, &config_layer_stack, &[]);
write_user_skill(&extra_root, "x", "extra-skill", "from extra root");
@@ -237,7 +249,7 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() {
let outcome_with_extra = skills_manager
.skills_for_cwd_with_extra_user_roots(
&base_input,
true,
/*force_reload*/ true,
std::slice::from_ref(&extra_root_path),
)
.await;
@@ -262,7 +274,9 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() {
config_layer_stack.clone(),
bundled_skills_enabled_from_stack(&config_layer_stack),
);
let outcome_without_extra = skills_manager.skills_for_cwd(&base_input, false).await;
let outcome_without_extra = skills_manager
.skills_for_cwd(&base_input, /*force_reload*/ false)
.await;
assert_eq!(outcome_without_extra.skills, outcome_with_extra.skills);
assert_eq!(outcome_without_extra.errors, outcome_with_extra.errors);
}
@@ -279,7 +293,10 @@ async fn skills_for_config_excludes_bundled_skills_when_disabled_in_config() {
)
.expect("write bundled skill");
let config_layer_stack = config_stack(&codex_home, "[skills.bundled]\nenabled = false\n");
let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), false);
let skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ false,
);
// Recreate the cached bundled skill after startup cleanup so this assertion exercises
// root selection rather than relying on directory removal succeeding.
@@ -312,7 +329,10 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() {
let extra_root_a = tempfile::tempdir().expect("tempdir");
let extra_root_b = tempfile::tempdir().expect("tempdir");
let config_layer_stack = config_stack(&codex_home, "");
let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true);
let skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ true,
);
let _ = skills_for_config_with_stack(&skills_manager, &cwd, &config_layer_stack, &[]);
write_user_skill(&extra_root_a, "x", "extra-skill-a", "from extra root a");
@@ -328,7 +348,7 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() {
let outcome_a = skills_manager
.skills_for_cwd_with_extra_user_roots(
&base_input,
true,
/*force_reload*/ true,
std::slice::from_ref(&extra_root_a_path),
)
.await;
@@ -349,7 +369,7 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() {
let outcome_b = skills_manager
.skills_for_cwd_with_extra_user_roots(
&base_input,
false,
/*force_reload*/ false,
std::slice::from_ref(&extra_root_b_path),
)
.await;
@@ -369,7 +389,7 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() {
let outcome_reloaded = skills_manager
.skills_for_cwd_with_extra_user_roots(
&base_input,
true,
/*force_reload*/ true,
std::slice::from_ref(&extra_root_b_path),
)
.await;
@@ -408,11 +428,13 @@ fn disabled_paths_for_skills_allows_session_flags_to_override_user_layer() {
.expect("user config path should be absolute");
let user_layer = ConfigLayerEntry::new(
ConfigLayerSource::User { file: user_file },
toml::from_str(&path_toggle_config(&skill_path, false)).expect("user layer toml"),
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false))
.expect("user layer toml"),
);
let session_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::from_str(&path_toggle_config(&skill_path, true)).expect("session layer toml"),
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ true))
.expect("session layer toml"),
);
let stack = ConfigLayerStack::new(
vec![user_layer, session_layer],
@@ -438,11 +460,13 @@ fn disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill(
.expect("user config path should be absolute");
let user_layer = ConfigLayerEntry::new(
ConfigLayerSource::User { file: user_file },
toml::from_str(&path_toggle_config(&skill_path, true)).expect("user layer toml"),
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ true))
.expect("user layer toml"),
);
let session_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::from_str(&path_toggle_config(&skill_path, false)).expect("session layer toml"),
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false))
.expect("session layer toml"),
);
let stack = ConfigLayerStack::new(
vec![user_layer, session_layer],
@@ -468,7 +492,8 @@ fn disabled_paths_for_skills_disables_matching_name_selectors() {
.expect("user config path should be absolute");
let user_layer = ConfigLayerEntry::new(
ConfigLayerSource::User { file: user_file },
toml::from_str(&name_toggle_config("github:yeet", false)).expect("user layer toml"),
toml::from_str(&name_toggle_config("github:yeet", /*enabled*/ false))
.expect("user layer toml"),
);
let stack = ConfigLayerStack::new(
vec![user_layer],
@@ -494,11 +519,13 @@ fn disabled_paths_for_skills_allows_name_selector_to_override_path_selector() {
.expect("user config path should be absolute");
let user_layer = ConfigLayerEntry::new(
ConfigLayerSource::User { file: user_file },
toml::from_str(&path_toggle_config(&skill_path, false)).expect("user layer toml"),
toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false))
.expect("user layer toml"),
);
let session_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::from_str(&name_toggle_config("github:yeet", true)).expect("session layer toml"),
toml::from_str(&name_toggle_config("github:yeet", /*enabled*/ true))
.expect("session layer toml"),
);
let stack = ConfigLayerStack::new(
vec![user_layer, session_layer],
@@ -527,12 +554,15 @@ async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill()
"---\nname: demo-skill\ndescription: demo description\n---\n\n# Body\n",
)
.expect("write skill");
let disabled_skill_config = path_toggle_config(&skill_path, false);
let enabled_skill_config = path_toggle_config(&skill_path, true);
let disabled_skill_config = path_toggle_config(&skill_path, /*enabled*/ false);
let enabled_skill_config = path_toggle_config(&skill_path, /*enabled*/ true);
let parent_stack = config_stack(&codex_home, &disabled_skill_config);
let child_stack =
config_stack_with_session_flags(&codex_home, &disabled_skill_config, &enabled_skill_config);
let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true);
let skills_manager = SkillsManager::new(
codex_home.path().to_path_buf(),
/*bundled_skills_enabled*/ true,
);
let parent_input = SkillsLoadInput::new(
cwd.path().to_path_buf(),
Vec::new(),
@@ -540,7 +570,9 @@ async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill()
bundled_skills_enabled_from_stack(&parent_stack),
);
let parent_outcome = skills_manager.skills_for_cwd(&parent_input, true).await;
let parent_outcome = skills_manager
.skills_for_cwd(&parent_input, /*force_reload*/ true)
.await;
let parent_skill = parent_outcome
.skills
.iter()
+57 -17
View File
@@ -289,7 +289,7 @@ async fn spawn_agent_errors_when_manager_dropped() {
let control = AgentControl::default();
let (_home, config) = test_config().await;
let err = control
.spawn_agent(config, text_input("hello"), None)
.spawn_agent(config, text_input("hello"), /*session_source*/ None)
.await
.expect_err("spawn_agent should fail without a manager");
assert_eq!(
@@ -423,7 +423,7 @@ async fn send_inter_agent_communication_without_turn_queues_message_without_trig
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
"hello from tests".to_string(),
false,
/*trigger_turn*/ false,
);
let submission_id = harness
@@ -535,7 +535,11 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
let harness = AgentControlHarness::new().await;
let thread_id = harness
.control
.spawn_agent(harness.config.clone(), text_input("spawned"), None)
.spawn_agent(
harness.config.clone(),
text_input("spawned"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed");
let _thread = harness
@@ -830,12 +834,20 @@ async fn spawn_agent_respects_max_threads_limit() {
.expect("start thread");
let first_agent_id = control
.spawn_agent(config.clone(), text_input("hello"), None)
.spawn_agent(
config.clone(),
text_input("hello"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed");
let err = control
.spawn_agent(config, text_input("hello again"), None)
.spawn_agent(
config,
text_input("hello again"),
/*session_source*/ None,
)
.await
.expect_err("spawn_agent should respect max threads");
let CodexErr::AgentLimitReached {
@@ -871,7 +883,11 @@ async fn spawn_agent_releases_slot_after_shutdown() {
let control = manager.agent_control();
let first_agent_id = control
.spawn_agent(config.clone(), text_input("hello"), None)
.spawn_agent(
config.clone(),
text_input("hello"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed");
let _ = control
@@ -880,7 +896,11 @@ async fn spawn_agent_releases_slot_after_shutdown() {
.expect("shutdown agent");
let second_agent_id = control
.spawn_agent(config.clone(), text_input("hello again"), None)
.spawn_agent(
config.clone(),
text_input("hello again"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed after shutdown");
let _ = control
@@ -909,12 +929,20 @@ async fn spawn_agent_limit_shared_across_clones() {
let cloned = control.clone();
let first_agent_id = cloned
.spawn_agent(config.clone(), text_input("hello"), None)
.spawn_agent(
config.clone(),
text_input("hello"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed");
let err = control
.spawn_agent(config, text_input("hello again"), None)
.spawn_agent(
config,
text_input("hello again"),
/*session_source*/ None,
)
.await
.expect_err("spawn_agent should respect shared guard");
let CodexErr::AgentLimitReached { max_threads } = err else {
@@ -947,7 +975,11 @@ async fn resume_agent_respects_max_threads_limit() {
let control = manager.agent_control();
let resumable_id = control
.spawn_agent(config.clone(), text_input("hello"), None)
.spawn_agent(
config.clone(),
text_input("hello"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed");
let _ = control
@@ -956,7 +988,11 @@ async fn resume_agent_respects_max_threads_limit() {
.expect("shutdown resumable thread");
let active_id = control
.spawn_agent(config.clone(), text_input("occupy"), None)
.spawn_agent(
config.clone(),
text_input("occupy"),
/*session_source*/ None,
)
.await
.expect("spawn_agent should succeed for active slot");
@@ -1002,7 +1038,7 @@ async fn resume_agent_releases_slot_after_resume_failure() {
.expect_err("resume should fail for missing rollout path");
let resumed_id = control
.spawn_agent(config, text_input("hello"), None)
.spawn_agent(config, text_input("hello"), /*session_source*/ None)
.await
.expect("spawn should succeed after failed resume");
let _ = control
@@ -1140,7 +1176,7 @@ async fn multi_agent_v2_completion_ignores_dead_direct_parent() {
AgentPath::root(),
Vec::new(),
"done".to_string(),
true,
/*trigger_turn*/ true,
)
));
assert!(!has_subagent_notification(&root_history_items));
@@ -1203,7 +1239,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() {
worker_path.clone(),
Vec::new(),
expected_message.clone(),
false,
/*trigger_turn*/ false,
),
},
);
@@ -1238,7 +1274,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() {
AgentPath::root(),
Vec::new(),
expected_message,
false,
/*trigger_turn*/ false,
)
));
}
@@ -1259,7 +1295,7 @@ async fn completion_watcher_notifies_parent_when_child_is_missing() {
agent_role: Some("explorer".to_string()),
})),
child_thread_id.to_string(),
None,
/*child_agent_path*/ None,
);
assert_eq!(wait_for_subagent_notification(&parent_thread).await, true);
@@ -1520,7 +1556,11 @@ async fn resume_agent_from_rollout_reads_archived_rollout_path() {
let harness = AgentControlHarness::new().await;
let child_thread_id = harness
.control
.spawn_agent(harness.config.clone(), text_input("hello"), None)
.spawn_agent(
harness.config.clone(),
text_input("hello"),
/*session_source*/ None,
)
.await
.expect("child spawn should succeed");
+46 -20
View File
@@ -16,11 +16,26 @@ fn agent_metadata(thread_id: ThreadId) -> AgentMetadata {
#[test]
fn format_agent_nickname_adds_ordinals_after_reset() {
assert_eq!(format_agent_nickname("Plato", 0), "Plato");
assert_eq!(format_agent_nickname("Plato", 1), "Plato the 2nd");
assert_eq!(format_agent_nickname("Plato", 2), "Plato the 3rd");
assert_eq!(format_agent_nickname("Plato", 10), "Plato the 11th");
assert_eq!(format_agent_nickname("Plato", 20), "Plato the 21st");
assert_eq!(
format_agent_nickname("Plato", /*nickname_reset_count*/ 0),
"Plato"
);
assert_eq!(
format_agent_nickname("Plato", /*nickname_reset_count*/ 1),
"Plato the 2nd"
);
assert_eq!(
format_agent_nickname("Plato", /*nickname_reset_count*/ 2),
"Plato the 3rd"
);
assert_eq!(
format_agent_nickname("Plato", /*nickname_reset_count*/ 10),
"Plato the 11th"
);
assert_eq!(
format_agent_nickname("Plato", /*nickname_reset_count*/ 20),
"Plato the 21st"
);
}
#[test]
@@ -39,7 +54,10 @@ fn thread_spawn_depth_increments_and_enforces_limit() {
});
let child_depth = next_thread_spawn_depth(&session_source);
assert_eq!(child_depth, 2);
assert!(exceeds_thread_spawn_depth_limit(child_depth, 1));
assert!(exceeds_thread_spawn_depth_limit(
child_depth,
/*max_depth*/ 1
));
}
#[test]
@@ -47,7 +65,9 @@ fn non_thread_spawn_subagents_default_to_depth_zero() {
let session_source = SessionSource::SubAgent(SubAgentSource::Review);
assert_eq!(session_depth(&session_source), 0);
assert_eq!(next_thread_spawn_depth(&session_source), 1);
assert!(!exceeds_thread_spawn_depth_limit(1, 1));
assert!(!exceeds_thread_spawn_depth_limit(
/*depth*/ 1, /*max_depth*/ 1
));
}
#[test]
@@ -142,14 +162,18 @@ fn release_is_idempotent_for_registered_threads() {
#[test]
fn failed_spawn_keeps_nickname_marked_used() {
let registry = Arc::new(AgentRegistry::default());
let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot");
let mut reservation = registry
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve slot");
let agent_nickname = reservation
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
.expect("reserve agent name");
assert_eq!(agent_nickname, "alpha");
drop(reservation);
let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot");
let mut reservation = registry
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve slot");
let agent_nickname = reservation
.reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None)
.expect("unused name should still be preferred");
@@ -160,7 +184,7 @@ fn failed_spawn_keeps_nickname_marked_used() {
fn agent_nickname_resets_used_pool_when_exhausted() {
let registry = Arc::new(AgentRegistry::default());
let mut first = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve first slot");
let first_name = first
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
@@ -170,7 +194,7 @@ fn agent_nickname_resets_used_pool_when_exhausted() {
assert_eq!(first_name, "alpha");
let mut second = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve second slot");
let second_name = second
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
@@ -188,7 +212,7 @@ fn released_nickname_stays_used_until_pool_reset() {
let registry = Arc::new(AgentRegistry::default());
let mut first = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve first slot");
let first_name = first
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
@@ -200,7 +224,7 @@ fn released_nickname_stays_used_until_pool_reset() {
registry.release_spawned_thread(first_id);
let mut second = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve second slot");
let second_name = second
.reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None)
@@ -211,7 +235,7 @@ fn released_nickname_stays_used_until_pool_reset() {
registry.release_spawned_thread(second_id);
let mut third = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve third slot");
let third_name = third
.reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None)
@@ -230,7 +254,7 @@ fn repeated_resets_advance_the_ordinal_suffix() {
let registry = Arc::new(AgentRegistry::default());
let mut first = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve first slot");
let first_name = first
.reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None)
@@ -241,7 +265,7 @@ fn repeated_resets_advance_the_ordinal_suffix() {
registry.release_spawned_thread(first_id);
let mut second = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve second slot");
let second_name = second
.reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None)
@@ -252,7 +276,7 @@ fn repeated_resets_advance_the_ordinal_suffix() {
registry.release_spawned_thread(second_id);
let mut third = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve third slot");
let third_name = third
.reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None)
@@ -282,7 +306,7 @@ fn register_root_thread_indexes_root_path() {
fn reserved_agent_path_is_released_when_spawn_fails() {
let registry = Arc::new(AgentRegistry::default());
let mut first = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve first slot");
first
.reserve_agent_path(&agent_path("/root/researcher"))
@@ -290,7 +314,7 @@ fn reserved_agent_path_is_released_when_spawn_fails() {
drop(first);
let mut second = registry
.reserve_spawn_slot(None)
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve second slot");
second
.reserve_agent_path(&agent_path("/root/researcher"))
@@ -301,7 +325,9 @@ fn reserved_agent_path_is_released_when_spawn_fails() {
fn committed_agent_path_is_indexed_until_release() {
let registry = Arc::new(AgentRegistry::default());
let thread_id = ThreadId::new();
let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot");
let mut reservation = registry
.reserve_spawn_slot(/*max_threads*/ None)
.expect("reserve slot");
reservation
.reserve_agent_path(&agent_path("/root/researcher"))
.expect("reserve path");
+13 -4
View File
@@ -40,7 +40,10 @@ async fn write_role_config(home: &TempDir, name: &str, contents: &str) -> PathBu
fn session_flags_layer_count(config: &Config) -> usize {
config
.config_layer_stack
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.filter(|layer| layer.name == ConfigLayerSource::SessionFlags)
.count()
@@ -51,7 +54,7 @@ async fn apply_role_defaults_to_default_and_leaves_config_unchanged() {
let (_home, mut config) = test_config_with_cli_overrides(Vec::new()).await;
let before = config.clone();
apply_role_to_config(&mut config, None)
apply_role_to_config(&mut config, /*role_name*/ None)
.await
.expect("default role should apply");
@@ -529,7 +532,10 @@ writable_roots = ["./sandbox-root"]
let role_layer = config
.config_layer_stack
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.rfind(|layer| layer.name == ConfigLayerSource::SessionFlags)
.expect("expected a session flags layer");
@@ -630,7 +636,10 @@ enabled = false
.expect("custom role should apply");
let plugins_manager = Arc::new(PluginsManager::new(home.path().to_path_buf()));
let skills_manager = SkillsManager::new(home.path().to_path_buf(), true);
let skills_manager = SkillsManager::new(
home.path().to_path_buf(),
/*bundled_skills_enabled*/ true,
);
let plugin_outcome = plugins_manager.plugins_for_config(&config);
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
+2 -1
View File
@@ -76,7 +76,8 @@ mod tests {
supports_websockets: false,
};
let telemetry = collect_auth_env_telemetry(&provider, false);
let telemetry =
collect_auth_env_telemetry(&provider, /*codex_api_key_env_enabled*/ false);
assert_eq!(
telemetry.provider_env_key_name,
+2 -1
View File
@@ -52,7 +52,8 @@ fn serializes_text_schema_with_strict_format() {
"required": ["answer"],
});
let text_controls =
create_text_param_for_request(None, &Some(schema.clone())).expect("text controls");
create_text_param_for_request(/*verbosity*/ None, &Some(schema.clone()))
.expect("text controls");
let req = ResponsesApiRequest {
model: "gpt-5.1".to_string(),
+15 -10
View File
@@ -16,14 +16,14 @@ fn test_model_client(session_source: SessionSource) -> ModelClient {
crate::model_provider_info::WireApi::Responses,
);
ModelClient::new(
None,
/*auth_manager*/ None,
ThreadId::new(),
provider,
session_source,
None,
false,
false,
None,
/*model_verbosity*/ None,
/*enable_request_compression*/ false,
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
)
}
@@ -62,11 +62,11 @@ fn test_session_telemetry() -> SessionTelemetry {
ThreadId::new(),
"gpt-test",
"gpt-test",
None,
None,
None,
/*account_id*/ None,
/*account_email*/ None,
/*auth_mode*/ None,
"test-originator".to_string(),
false,
/*log_user_prompts*/ false,
"test-terminal".to_string(),
SessionSource::Cli,
)
@@ -91,7 +91,12 @@ async fn summarize_memories_returns_empty_for_empty_input() {
let session_telemetry = test_session_telemetry();
let output = client
.summarize_memories(Vec::new(), &model_info, None, &session_telemetry)
.summarize_memories(
Vec::new(),
&model_info,
/*effort*/ None,
&session_telemetry,
)
.await
.expect("empty summarize request should succeed");
assert_eq!(output.len(), 0);
@@ -41,7 +41,7 @@ fn inter_agent_assistant_message(text: &str) -> ResponseItem {
AgentPath::root().join("worker").unwrap(),
Vec::new(),
text.to_string(),
true,
/*trigger_turn*/ true,
);
ResponseItem::Message {
id: None,
+91 -62
View File
@@ -247,17 +247,17 @@ async fn interrupting_regular_turn_waiting_on_startup_prewarm_emits_turn_aborted
fn test_model_client_session() -> crate::client::ModelClientSession {
crate::client::ModelClient::new(
None,
/*auth_manager*/ None,
ThreadId::try_from("00000000-0000-4000-8000-000000000001")
.expect("test thread id should be valid"),
crate::model_provider_info::ModelProviderInfo::create_openai_provider(
/* base_url */ None,
/* base_url */ /*base_url*/ None,
),
codex_protocol::protocol::SessionSource::Exec,
None,
false,
false,
None,
/*model_verbosity*/ None,
/*enable_request_compression*/ false,
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
)
.new_session()
}
@@ -313,7 +313,7 @@ fn make_connector(id: &str, name: &str) -> AppInfo {
#[test]
fn assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text() {
let mut parsers = AssistantMessageStreamParsers::new(false);
let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ false);
let item_id = "msg-1";
let seeded = parsers.seed_item_text(item_id, "hello <oai-mem-citation>doc");
@@ -330,7 +330,7 @@ fn assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text()
#[test]
fn assistant_message_stream_parsers_seed_buffered_prefix_stays_out_of_finish_tail() {
let mut parsers = AssistantMessageStreamParsers::new(false);
let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ false);
let item_id = "msg-1";
let seeded = parsers.seed_item_text(item_id, "hello <oai-mem-");
@@ -347,7 +347,7 @@ fn assistant_message_stream_parsers_seed_buffered_prefix_stays_out_of_finish_tai
#[test]
fn assistant_message_stream_parsers_seed_plan_parser_across_added_and_delta_boundaries() {
let mut parsers = AssistantMessageStreamParsers::new(true);
let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ true);
let item_id = "msg-1";
let seeded = parsers.seed_item_text(item_id, "Intro\n<proposed");
@@ -449,7 +449,7 @@ fn validated_network_policy_amendment_host_rejects_mismatch() {
async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyhow::Result<()> {
let spec = crate::config::NetworkProxySpec::from_config_and_constraints(
NetworkProxyConfig::default(),
None,
/*requirements*/ None,
&SandboxPolicy::new_workspace_write_policy(),
)?;
let mut exec_policy = Policy::empty();
@@ -457,16 +457,16 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho
"example.com",
NetworkRuleProtocol::Https,
Decision::Allow,
None,
/*justification*/ None,
)?;
let (started_proxy, _) = Session::start_managed_network_proxy(
&spec,
&exec_policy,
&SandboxPolicy::new_workspace_write_policy(),
None,
None,
false,
/*network_policy_decider*/ None,
/*blocked_request_observer*/ None,
/*managed_network_requirements_enabled*/ false,
crate::config::NetworkProxyAuditMetadata::default(),
)
.await?;
@@ -501,16 +501,16 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
"example.com",
NetworkRuleProtocol::Https,
Decision::Allow,
None,
/*justification*/ None,
)?;
let (started_proxy, _) = Session::start_managed_network_proxy(
&spec,
&exec_policy,
&SandboxPolicy::new_workspace_write_policy(),
None,
None,
false,
/*network_policy_decider*/ None,
/*blocked_request_observer*/ None,
/*managed_network_requirements_enabled*/ false,
crate::config::NetworkProxyAuditMetadata::default(),
)
.await?;
@@ -737,7 +737,9 @@ fn non_app_mcp_tools_remain_visible_without_search_selection() {
),
(
"mcp__rmcp__echo".to_string(),
make_mcp_tool("rmcp", "echo", None, None),
make_mcp_tool(
"rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None,
),
),
]);
@@ -785,7 +787,9 @@ fn search_tool_selection_keeps_codex_apps_tools_without_mentions() {
),
(
"mcp__rmcp__echo".to_string(),
make_mcp_tool("rmcp", "echo", None, None),
make_mcp_tool(
"rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None,
),
),
]);
@@ -835,7 +839,9 @@ fn apps_mentions_add_codex_apps_tools_to_search_selected_set() {
),
(
"mcp__rmcp__echo".to_string(),
make_mcp_tool("rmcp", "echo", None, None),
make_mcp_tool(
"rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None,
),
),
]);
@@ -1179,7 +1185,13 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
codex_config::Constrained::allow_any(AskForApproval::UnlessTrusted);
let forked = initial
.thread_manager
.fork_thread(usize::MAX, fork_config, rollout_path, false, None)
.fork_thread(
usize::MAX,
fork_config,
rollout_path,
/*persist_extended_history*/ false,
/*parent_trace*/ None,
)
.await?;
let collaboration_mode = CollaborationMode {
@@ -1351,7 +1363,7 @@ async fn thread_rollback_drops_last_turn_from_history() {
state.set_reference_context_item(Some(tc.to_turn_context_item()));
}
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let rollback_event = wait_for_thread_rolled_back(&rx).await;
assert_eq!(rollback_event.num_turns, 1);
@@ -1398,7 +1410,7 @@ async fn thread_rollback_clears_history_when_num_turns_exceeds_existing_turns()
.collect();
sess.persist_rollout_items(&rollout_items).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), 99).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 99).await;
let rollback_event = wait_for_thread_rolled_back(&rx).await;
assert_eq!(rollback_event.num_turns, 99);
@@ -1415,7 +1427,7 @@ async fn thread_rollback_fails_without_persisted_rollout_path() {
sess.record_into_history(&initial_context, tc.as_ref())
.await;
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let error_event = wait_for_thread_rollback_failed(&rx).await;
assert_eq!(
@@ -1509,7 +1521,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context
}))
.await;
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let rollback_event = wait_for_thread_rolled_back(&rx).await;
assert_eq!(rollback_event.num_turns, 1);
@@ -1617,7 +1629,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio
)
.await;
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let rollback_event = wait_for_thread_rolled_back(&rx).await;
assert_eq!(rollback_event.num_turns, 1);
@@ -1695,10 +1707,10 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
])
.await;
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let first_rollback = wait_for_thread_rolled_back(&rx).await;
assert_eq!(first_rollback.num_turns, 1);
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let second_rollback = wait_for_thread_rolled_back(&rx).await;
assert_eq!(second_rollback.num_turns, 1);
@@ -1733,7 +1745,7 @@ async fn thread_rollback_fails_when_turn_in_progress() {
.await;
*sess.active_turn.lock().await = Some(crate::state::ActiveTurn::default());
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
let error_event = wait_for_thread_rollback_failed(&rx).await;
assert_eq!(
@@ -1753,7 +1765,7 @@ async fn thread_rollback_fails_when_num_turns_is_zero() {
sess.record_into_history(&initial_context, tc.as_ref())
.await;
handlers::thread_rollback(&sess, "sub-1".to_string(), 0).await;
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 0).await;
let error_event = wait_for_thread_rollback_failed(&rx).await;
assert_eq!(error_event.message, "num_turns must be >= 1");
@@ -2152,14 +2164,14 @@ async fn attach_rollout_recorder(session: &Arc<Session>) -> PathBuf {
config.as_ref(),
RolloutRecorderParams::new(
ThreadId::default(),
None,
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
Vec::new(),
EventPersistenceMode::Limited,
),
None,
None,
/*state_db_ctx*/ None,
/*state_builder*/ None,
)
.await
.expect("create rollout recorder");
@@ -2198,11 +2210,11 @@ fn session_telemetry(
conversation_id,
ModelsManager::get_model_offline_for_tests(config.model.as_deref()).as_str(),
model_info.slug.as_str(),
None,
/*account_id*/ None,
Some("test@test.com".to_string()),
Some(TelemetryAuthMode::Chatgpt),
"test_originator".to_string(),
false,
/*log_user_prompts*/ false,
"test".to_string(),
session_source,
)
@@ -2325,7 +2337,7 @@ async fn new_default_turn_uses_config_aware_skills_for_role_overrides() {
.skills_manager
.skills_for_cwd(
&crate::skills_load_input_from_config(&parent_config, Vec::new()),
true,
/*force_reload*/ true,
)
.await;
let parent_skill = parent_outcome
@@ -2473,7 +2485,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
let models_manager = Arc::new(ModelsManager::new(
config.codex_home.clone(),
auth_manager.clone(),
None,
/*model_catalog*/ None,
CollaborationModesConfig::default(),
));
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
@@ -2522,7 +2534,10 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
/*bundled_skills_enabled*/ true,
));
let result = Session::new(
session_configuration,
Arc::clone(&config),
@@ -2563,7 +2578,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let models_manager = Arc::new(ModelsManager::new(
config.codex_home.clone(),
auth_manager.clone(),
None,
/*model_catalog*/ None,
CollaborationModesConfig::default(),
));
let agent_control = AgentControl::default();
@@ -2626,7 +2641,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let state = SessionState::new(session_configuration.clone());
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
/*bundled_skills_enabled*/ true,
));
let network_approval = Arc::new(NetworkApprovalService::default());
let environment = Arc::new(
codex_exec_server::Environment::create(/*exec_server_url*/ None)
@@ -2712,7 +2730,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
per_turn_config,
model_info,
&models_manager,
None,
/*network*/ None,
environment,
"turn_id".to_string(),
Arc::clone(&js_repl),
@@ -3400,7 +3418,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
let models_manager = Arc::new(ModelsManager::new(
config.codex_home.clone(),
auth_manager.clone(),
None,
/*model_catalog*/ None,
CollaborationModesConfig::default(),
));
let agent_control = AgentControl::default();
@@ -3463,7 +3481,10 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
let state = SessionState::new(session_configuration.clone());
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
/*bundled_skills_enabled*/ true,
));
let network_approval = Arc::new(NetworkApprovalService::default());
let environment = Arc::new(
codex_exec_server::Environment::create(/*exec_server_url*/ None)
@@ -3549,7 +3570,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
per_turn_config,
model_info,
&models_manager,
None,
/*network*/ None,
environment,
"turn_id".to_string(),
Arc::clone(&js_repl),
@@ -3659,7 +3680,8 @@ async fn record_model_warning_appends_user_message() {
#[tokio::test]
async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_tasks() {
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
sess.set_previous_turn_settings(None).await;
sess.set_previous_turn_settings(/*previous_turn_settings*/ None)
.await;
let input = vec![UserInput::Text {
text: "hello".to_string(),
text_elements: Vec::new(),
@@ -3712,7 +3734,10 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes(
));
let layers = config
.config_layer_stack
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.cloned()
.collect();
@@ -3909,7 +3934,7 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist
revised_prompt: Some("a tiny blue square".to_string()),
result: "Zm9v".to_string(),
}],
None,
/*reference_context_item*/ None,
)
.await;
@@ -3963,7 +3988,7 @@ async fn handle_output_item_done_records_image_save_history_message() {
tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)),
cancellation_token: CancellationToken::new(),
};
handle_output_item_done(&mut ctx, item.clone(), None)
handle_output_item_done(&mut ctx, item.clone(), /*previously_active_item*/ None)
.await
.expect("image generation item should succeed");
@@ -4020,7 +4045,7 @@ async fn handle_output_item_done_skips_image_save_message_when_save_fails() {
tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)),
cancellation_token: CancellationToken::new(),
};
handle_output_item_done(&mut ctx, item.clone(), None)
handle_output_item_done(&mut ctx, item.clone(), /*previously_active_item*/ None)
.await
.expect("image generation item should still complete");
@@ -4112,10 +4137,13 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
.await;
{
let mut state = session.state.lock().await;
state.set_reference_context_item(None);
state.set_reference_context_item(/*item*/ None);
}
session
.replace_history(vec![compacted_summary.clone()], None)
.replace_history(
vec![compacted_summary.clone()],
/*reference_context_item*/ None,
)
.await;
session
@@ -4150,14 +4178,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
config.as_ref(),
RolloutRecorderParams::new(
ThreadId::default(),
None,
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
Vec::new(),
EventPersistenceMode::Limited,
),
None,
None,
/*state_db_ctx*/ None,
/*state_builder*/ None,
)
.await
.expect("create rollout recorder");
@@ -4247,14 +4275,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
config.as_ref(),
RolloutRecorderParams::new(
ThreadId::default(),
None,
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
Vec::new(),
EventPersistenceMode::Limited,
),
None,
None,
/*state_db_ctx*/ None,
/*state_builder*/ None,
)
.await
.expect("create rollout recorder");
@@ -4276,7 +4304,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
.await;
{
let mut state = session.state.lock().await;
state.set_reference_context_item(None);
state.set_reference_context_item(/*item*/ None);
}
session
@@ -4315,7 +4343,7 @@ async fn run_user_shell_command_does_not_set_reference_context_item() {
let (session, _turn_context, rx) = make_session_and_context_with_rx().await;
{
let mut state = session.state.lock().await;
state.set_reference_context_item(None);
state.set_reference_context_item(/*item*/ None);
}
handlers::run_user_shell_command(&session, "sub-id".to_string(), "echo shell".to_string())
@@ -4468,7 +4496,8 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input()
.await
.expect("inject pending input into active turn");
sess.on_task_finished(Arc::clone(&tc), None).await;
sess.on_task_finished(Arc::clone(&tc), /*last_agent_message*/ None)
.await;
let history = sess.clone_history().await;
let expected = ResponseItem::Message {
@@ -4560,7 +4589,7 @@ async fn steer_input_requires_active_turn() {
}];
let err = sess
.steer_input(input, None)
.steer_input(input, /*expected_turn_id*/ None)
.await
.expect_err("steering without active turn should fail");
+5 -2
View File
@@ -425,11 +425,14 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
let models_manager = Arc::new(ModelsManager::new(
config.codex_home.clone(),
auth_manager.clone(),
None,
/*model_catalog*/ None,
CollaborationModesConfig::default(),
));
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
let skills_manager = Arc::new(SkillsManager::new(
config.codex_home.clone(),
/*bundled_skills_enabled*/ true,
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_watcher = Arc::new(SkillsWatcher::noop());
@@ -11,7 +11,7 @@ fn blank_attribution_disables_trailer_prompt() {
#[test]
fn default_attribution_uses_codex_trailer() {
assert_eq!(
build_commit_message_trailer(None).as_deref(),
build_commit_message_trailer(/*config_attribution*/ None).as_deref(),
Some("Co-authored-by: Codex <noreply@openai.com>")
);
}
@@ -19,7 +19,7 @@ fn default_attribution_uses_codex_trailer() {
#[test]
fn resolve_value_handles_default_custom_and_blank() {
assert_eq!(
resolve_attribution_value(None),
resolve_attribution_value(/*config_attribution*/ None),
Some("Codex <noreply@openai.com>".to_string())
);
assert_eq!(
+20 -8
View File
@@ -216,8 +216,11 @@ async fn process_compacted_history_replaces_developer_messages() {
phase: None,
},
];
let (refreshed, mut expected) =
process_compacted_history_with_test_session(compacted_history, None).await;
let (refreshed, mut expected) = process_compacted_history_with_test_session(
compacted_history,
/*previous_turn_settings*/ None,
)
.await;
expected.push(ResponseItem::Message {
id: None,
role: "user".to_string(),
@@ -241,8 +244,11 @@ async fn process_compacted_history_reinjects_full_initial_context() {
end_turn: None,
phase: None,
}];
let (refreshed, mut expected) =
process_compacted_history_with_test_session(compacted_history, None).await;
let (refreshed, mut expected) = process_compacted_history_with_test_session(
compacted_history,
/*previous_turn_settings*/ None,
)
.await;
expected.push(ResponseItem::Message {
id: None,
role: "user".to_string(),
@@ -317,8 +323,11 @@ keep me updated
phase: None,
},
];
let (refreshed, mut expected) =
process_compacted_history_with_test_session(compacted_history, None).await;
let (refreshed, mut expected) = process_compacted_history_with_test_session(
compacted_history,
/*previous_turn_settings*/ None,
)
.await;
expected.push(ResponseItem::Message {
id: None,
role: "user".to_string(),
@@ -363,8 +372,11 @@ async fn process_compacted_history_inserts_context_before_last_real_user_message
},
];
let (refreshed, initial_context) =
process_compacted_history_with_test_session(compacted_history, None).await;
let (refreshed, initial_context) = process_compacted_history_with_test_session(
compacted_history,
/*previous_turn_settings*/ None,
)
.await;
let mut expected = vec![
ResponseItem::Message {
id: None,
+71 -47
View File
@@ -507,7 +507,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(None),
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -709,7 +709,10 @@ fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<()> {
config.permissions.file_system_sandbox_policy,
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::unknown(":future_special_path", None),
value: FileSystemSpecialPath::unknown(
":future_special_path",
/*subpath*/ None
),
},
access: FileSystemAccessMode::Read,
}]),
@@ -976,10 +979,10 @@ network_access = false # This should be ignored.
let sandbox_mode_override = None;
let resolution = sandbox_full_access_cfg.derive_sandbox_policy(
sandbox_mode_override,
None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&PathBuf::from("/tmp/test"),
None,
/*sandbox_policy_constraint*/ None,
);
assert_eq!(resolution, SandboxPolicy::DangerFullAccess);
@@ -995,10 +998,10 @@ network_access = true # This should be ignored.
let sandbox_mode_override = None;
let resolution = sandbox_read_only_cfg.derive_sandbox_policy(
sandbox_mode_override,
None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&PathBuf::from("/tmp/test"),
None,
/*sandbox_policy_constraint*/ None,
);
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
@@ -1022,10 +1025,10 @@ exclude_slash_tmp = true
let sandbox_mode_override = None;
let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy(
sandbox_mode_override,
None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&PathBuf::from("/tmp/test"),
None,
/*sandbox_policy_constraint*/ None,
);
if cfg!(target_os = "windows") {
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
@@ -1064,10 +1067,10 @@ trust_level = "trusted"
let sandbox_mode_override = None;
let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy(
sandbox_mode_override,
None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&PathBuf::from("/tmp/test"),
None,
/*sandbox_policy_constraint*/ None,
);
if cfg!(target_os = "windows") {
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
@@ -1245,7 +1248,7 @@ fn filter_mcp_servers_by_allowlist_allows_all_when_unset() {
("server-b".to_string(), http_mcp("https://example.com/b")),
]);
filter_mcp_servers_by_requirements(&mut servers, None);
filter_mcp_servers_by_requirements(&mut servers, /*mcp_requirements*/ None);
assert_eq!(
servers
@@ -1874,7 +1877,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -1904,7 +1907,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
let empty = BTreeMap::new();
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(empty.clone())],
)?;
let loaded = load_global_mcp_servers(codex_home.path()).await?;
@@ -2101,7 +2104,7 @@ async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2174,7 +2177,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2227,7 +2230,7 @@ async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2278,7 +2281,7 @@ async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2344,7 +2347,7 @@ async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyh
)]);
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2424,7 +2427,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
let serialized_with_optional = std::fs::read_to_string(&config_path)?;
@@ -2455,7 +2458,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
);
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2546,7 +2549,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2630,7 +2633,7 @@ async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2677,7 +2680,7 @@ async fn replace_mcp_servers_serializes_required_flag() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2724,7 +2727,7 @@ async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2775,7 +2778,7 @@ async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyh
apply_blocking(
codex_home.path(),
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)?;
@@ -2929,7 +2932,7 @@ async fn set_feature_enabled_updates_profile() -> anyhow::Result<()> {
ConfigEditsBuilder::new(codex_home.path())
.with_profile(Some("dev"))
.set_feature_enabled("guardian_approval", true)
.set_feature_enabled("guardian_approval", /*enabled*/ true)
.apply()
.await?;
@@ -2965,13 +2968,13 @@ async fn set_feature_enabled_persists_default_false_feature_disable_in_profile()
ConfigEditsBuilder::new(codex_home.path())
.with_profile(Some("dev"))
.set_feature_enabled("guardian_approval", true)
.set_feature_enabled("guardian_approval", /*enabled*/ true)
.apply()
.await?;
ConfigEditsBuilder::new(codex_home.path())
.with_profile(Some("dev"))
.set_feature_enabled("guardian_approval", false)
.set_feature_enabled("guardian_approval", /*enabled*/ false)
.apply()
.await?;
@@ -3005,13 +3008,13 @@ async fn set_feature_enabled_profile_disable_overrides_root_enable() -> anyhow::
let codex_home = TempDir::new()?;
ConfigEditsBuilder::new(codex_home.path())
.set_feature_enabled("guardian_approval", true)
.set_feature_enabled("guardian_approval", /*enabled*/ true)
.apply()
.await?;
ConfigEditsBuilder::new(codex_home.path())
.with_profile(Some("dev"))
.set_feature_enabled("guardian_approval", false)
.set_feature_enabled("guardian_approval", /*enabled*/ false)
.apply()
.await?;
@@ -4323,7 +4326,8 @@ model_verbosity = "high"
supports_websockets: false,
};
let model_provider_map = {
let mut model_provider_map = built_in_model_providers(/* openai_base_url */ None);
let mut model_provider_map =
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None);
model_provider_map.insert("openai-custom".to_string(), openai_custom_provider.clone());
model_provider_map
};
@@ -4392,7 +4396,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
windows_sandbox_private_desktop: true,
},
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(None),
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
notify: None,
cwd: fixture.cwd(),
@@ -4534,7 +4538,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
windows_sandbox_private_desktop: true,
},
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(None),
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
notify: None,
cwd: fixture.cwd(),
@@ -4674,7 +4678,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
windows_sandbox_private_desktop: true,
},
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(None),
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
notify: None,
cwd: fixture.cwd(),
@@ -4800,7 +4804,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
windows_sandbox_private_desktop: true,
},
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(None),
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
notify: None,
cwd: fixture.cwd(),
@@ -5142,11 +5146,11 @@ trust_level = "untrusted"
.expect("TOML deserialization should succeed");
let resolution = cfg.derive_sandbox_policy(
None,
None,
/*sandbox_mode_override*/ None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&PathBuf::from("/tmp/test"),
None,
/*sandbox_policy_constraint*/ None,
);
// Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade)
@@ -5194,8 +5198,8 @@ fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defaults()
})?;
let resolution = cfg.derive_sandbox_policy(
None,
None,
/*sandbox_mode_override*/ None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&project_path,
Some(&constrained),
@@ -5234,8 +5238,8 @@ fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback()
})?;
let resolution = cfg.derive_sandbox_policy(
None,
None,
/*sandbox_mode_override*/ None,
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
&project_path,
Some(&constrained),
@@ -5252,7 +5256,11 @@ fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback()
#[test]
fn test_resolve_oss_provider_explicit_override() {
let config_toml = ConfigToml::default();
let result = resolve_oss_provider(Some("custom-provider"), &config_toml, None);
let result = resolve_oss_provider(
Some("custom-provider"),
&config_toml,
/*config_profile*/ None,
);
assert_eq!(result, Some("custom-provider".to_string()));
}
@@ -5269,7 +5277,11 @@ fn test_resolve_oss_provider_from_profile() {
..Default::default()
};
let result = resolve_oss_provider(None, &config_toml, Some("test-profile".to_string()));
let result = resolve_oss_provider(
/*explicit_provider*/ None,
&config_toml,
Some("test-profile".to_string()),
);
assert_eq!(result, Some("profile-provider".to_string()));
}
@@ -5280,7 +5292,11 @@ fn test_resolve_oss_provider_from_global_config() {
..Default::default()
};
let result = resolve_oss_provider(None, &config_toml, None);
let result = resolve_oss_provider(
/*explicit_provider*/ None,
&config_toml,
/*config_profile*/ None,
);
assert_eq!(result, Some("global-provider".to_string()));
}
@@ -5295,14 +5311,22 @@ fn test_resolve_oss_provider_profile_fallback_to_global() {
..Default::default()
};
let result = resolve_oss_provider(None, &config_toml, Some("test-profile".to_string()));
let result = resolve_oss_provider(
/*explicit_provider*/ None,
&config_toml,
Some("test-profile".to_string()),
);
assert_eq!(result, Some("global-provider".to_string()));
}
#[test]
fn test_resolve_oss_provider_none_when_not_configured() {
let config_toml = ConfigToml::default();
let result = resolve_oss_provider(None, &config_toml, None);
let result = resolve_oss_provider(
/*explicit_provider*/ None,
&config_toml,
/*config_profile*/ None,
);
assert_eq!(result, None);
}
+48 -23
View File
@@ -16,7 +16,7 @@ fn blocking_set_model_top_level() {
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetModel {
model: Some("gpt-5.1-codex".to_string()),
effort: Some(ReasoningEffort::High),
@@ -150,7 +150,7 @@ profiles = { fast = { model = "gpt-4o", sandbox_mode = "strict" } }
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetModel {
model: Some("o4-mini".to_string()),
effort: None,
@@ -195,7 +195,7 @@ fn blocking_set_model_writes_through_symlink_chain() {
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetModel {
model: Some("gpt-5.1-codex".to_string()),
effort: Some(ReasoningEffort::High),
@@ -228,7 +228,7 @@ fn blocking_set_model_replaces_symlink_on_cycle() {
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetModel {
model: Some("gpt-5.1-codex".to_string()),
effort: None,
@@ -267,7 +267,7 @@ network_access = false
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[
ConfigEdit::SetPath {
segments: vec![
@@ -322,7 +322,7 @@ profiles = { fast = { model = "gpt-4o", sandbox_mode = "strict" } }
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetModel {
model: None,
effort: Some(ReasoningEffort::High),
@@ -356,7 +356,7 @@ model_reasoning_effort = "low"
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetModel {
model: Some("o5-preview".to_string()),
effort: Some(ReasoningEffort::Minimal),
@@ -420,7 +420,7 @@ existing = "value"
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetNoticeHideFullAccessWarning(true)],
)
.expect("persist");
@@ -450,7 +450,7 @@ existing = "value"
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetNoticeHideRateLimitModelNudge(true)],
)
.expect("persist");
@@ -476,7 +476,7 @@ existing = "value"
.expect("seed");
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetNoticeHideModelMigrationPrompt(
"hide_gpt5_1_migration_prompt".to_string(),
true,
@@ -505,7 +505,7 @@ existing = "value"
.expect("seed");
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetNoticeHideModelMigrationPrompt(
"hide_gpt-5.1-codex-max_migration_prompt".to_string(),
true,
@@ -534,7 +534,7 @@ existing = "value"
.expect("seed");
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::RecordModelMigrationSeen {
from: "gpt-5".to_string(),
to: "gpt-5.1".to_string(),
@@ -616,7 +616,7 @@ fn blocking_replace_mcp_servers_round_trips() {
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
)
.expect("persist");
@@ -681,7 +681,12 @@ fn blocking_replace_mcp_servers_serializes_tool_approval_overrides() {
},
);
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
apply_blocking(
codex_home,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers)],
)
.expect("persist");
let raw = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = "\
@@ -731,7 +736,12 @@ foo = { command = "cmd" }
},
);
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
apply_blocking(
codex_home,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers)],
)
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"[mcp_servers]
@@ -777,7 +787,12 @@ foo = { command = "cmd" } # keep me
},
);
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
apply_blocking(
codex_home,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers)],
)
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"[mcp_servers]
@@ -822,7 +837,12 @@ foo = { command = "cmd", args = ["--flag"] } # keep me
},
);
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
apply_blocking(
codex_home,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers)],
)
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"[mcp_servers]
@@ -868,7 +888,12 @@ foo = { command = "cmd" }
},
);
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
apply_blocking(
codex_home,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(servers)],
)
.expect("persist");
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
let expected = r#"[mcp_servers]
@@ -885,7 +910,7 @@ fn blocking_clear_path_noop_when_missing() {
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::ClearPath {
segments: vec!["missing".to_string()],
}],
@@ -906,7 +931,7 @@ fn blocking_set_path_updates_notifications() {
let item = value(false);
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::SetPath {
segments: vec!["tui".to_string(), "notifications".to_string()],
value: item,
@@ -982,7 +1007,7 @@ async fn blocking_set_asynchronous_helpers_available() {
let codex_home = tmp.path().to_path_buf();
ConfigEditsBuilder::new(&codex_home)
.set_hide_full_access_warning(true)
.set_hide_full_access_warning(/*acknowledged*/ true)
.apply()
.await
.expect("persist");
@@ -1024,7 +1049,7 @@ fn blocking_builder_set_realtime_audio_persists_and_clears() {
);
ConfigEditsBuilder::new(codex_home)
.set_realtime_microphone(None)
.set_realtime_microphone(/*microphone*/ None)
.apply_blocking()
.expect("clear realtime microphone");
@@ -1053,7 +1078,7 @@ fn replace_mcp_servers_blocking_clears_table_when_empty() {
apply_blocking(
codex_home,
None,
/*profile*/ None,
&[ConfigEdit::ReplaceMcpServers(BTreeMap::new())],
)
.expect("persist");
+4 -1
View File
@@ -96,7 +96,10 @@ impl ManagedFeatures {
impl From<Features> for ManagedFeatures {
fn from(features: Features) -> Self {
Self {
value: ConstrainedWithSource::new(Constrained::allow_any(features), None),
value: ConstrainedWithSource::new(
Constrained::allow_any(features),
/*source*/ None,
),
pinned_features: BTreeMap::new(),
}
}
@@ -9,8 +9,10 @@ use tempfile::TempDir;
#[test]
fn normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths() {
let parsed =
normalize_absolute_path_for_platform(r"\\?\D:\c\x\worktrees\2508\swift-base", true);
let parsed = normalize_absolute_path_for_platform(
r"\\?\D:\c\x\worktrees\2508\swift-base",
/*is_windows*/ true,
);
assert_eq!(parsed, PathBuf::from(r"D:\c\x\worktrees\2508\swift-base"));
}
+56 -14
View File
@@ -797,7 +797,7 @@ async fn load_config_layers_fails_when_cloud_requirements_loader_fails() -> anyh
CloudRequirementsLoader::new(async {
Err(CloudRequirementsLoadError::new(
codex_config::CloudRequirementsLoadErrorCode::RequestFailed,
None,
/*status_code*/ None,
"cloud requirements failed",
))
}),
@@ -833,7 +833,13 @@ async fn project_layers_prefer_closest_cwd() -> std::io::Result<()> {
let codex_home = tmp.path().join("home");
tokio::fs::create_dir_all(&codex_home).await?;
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
make_config_for_test(
&codex_home,
&project_root,
TrustLevel::Trusted,
/*project_root_markers*/ None,
)
.await?;
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
let layers = load_config_layers_state(
&codex_home,
@@ -899,7 +905,13 @@ model_instructions_file = "child.txt"
let codex_home = tmp.path().join("home");
tokio::fs::create_dir_all(&codex_home).await?;
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
make_config_for_test(
&codex_home,
&project_root,
TrustLevel::Trusted,
/*project_root_markers*/ None,
)
.await?;
let config = ConfigBuilder::default()
.codex_home(codex_home)
@@ -965,7 +977,13 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s
let codex_home = tmp.path().join("home");
tokio::fs::create_dir_all(&codex_home).await?;
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
make_config_for_test(
&codex_home,
&project_root,
TrustLevel::Trusted,
/*project_root_markers*/ None,
)
.await?;
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
let layers = load_config_layers_state(
&codex_home,
@@ -1018,7 +1036,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R
let project_layers: Vec<_> = layers
.get_layers(
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
true,
/*include_disabled*/ true,
)
.into_iter()
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
@@ -1046,7 +1064,13 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul
tokio::fs::write(nested_dot_codex.join(CONFIG_TOML_FILE), "foo = \"child\"\n").await?;
tokio::fs::create_dir_all(&project_dot_codex).await?;
make_config_for_test(&project_dot_codex, &project_root, TrustLevel::Trusted, None).await?;
make_config_for_test(
&project_dot_codex,
&project_root,
TrustLevel::Trusted,
/*project_root_markers*/ None,
)
.await?;
let user_config_path = project_dot_codex.join(CONFIG_TOML_FILE);
let user_config_contents = tokio::fs::read_to_string(&user_config_path).await?;
tokio::fs::write(
@@ -1068,7 +1092,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul
let project_layers: Vec<_> = layers
.get_layers(
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
true,
/*include_disabled*/ true,
)
.into_iter()
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
@@ -1115,7 +1139,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<
&codex_home_untrusted,
&project_root,
TrustLevel::Untrusted,
None,
/*project_root_markers*/ None,
)
.await?;
let untrusted_config_path = codex_home_untrusted.join(CONFIG_TOML_FILE);
@@ -1137,7 +1161,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<
let project_layers_untrusted: Vec<_> = layers_untrusted
.get_layers(
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
true,
/*include_disabled*/ true,
)
.into_iter()
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
@@ -1175,7 +1199,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<
let project_layers_unknown: Vec<_> = layers_unknown
.get_layers(
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
true,
/*include_disabled*/ true,
)
.into_iter()
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
@@ -1218,7 +1242,13 @@ enabled = false
"#,
)
.await?;
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
make_config_for_test(
&codex_home,
&project_root,
TrustLevel::Trusted,
/*project_root_markers*/ None,
)
.await?;
let config = ConfigBuilder::default()
.codex_home(codex_home)
@@ -1303,7 +1333,13 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io::
let config_path = codex_home.join(CONFIG_TOML_FILE);
if let Some(trust_level) = trust_level {
make_config_for_test(&codex_home, &project_root, trust_level, None).await?;
make_config_for_test(
&codex_home,
&project_root,
trust_level,
/*project_root_markers*/ None,
)
.await?;
let config_contents = tokio::fs::read_to_string(&config_path).await?;
tokio::fs::write(&config_path, format!("foo = \"user\"\n{config_contents}")).await?;
} else {
@@ -1321,7 +1357,7 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io::
let project_layers: Vec<_> = layers
.get_layers(
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
true,
/*include_disabled*/ true,
)
.into_iter()
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
@@ -1358,7 +1394,13 @@ async fn cli_overrides_with_relative_paths_do_not_break_trust_check() -> std::io
let codex_home = tmp.path().join("home");
tokio::fs::create_dir_all(&codex_home).await?;
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
make_config_for_test(
&codex_home,
&project_root,
TrustLevel::Trusted,
/*project_root_markers*/ None,
)
.await?;
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
let cli_overrides = vec![(
+53 -21
View File
@@ -171,7 +171,7 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
codex_app_tool(
"calendar_list_events",
"calendar",
None,
/*connector_name*/ None,
&["sample", "sample"],
),
),
@@ -229,8 +229,8 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
.build()
.await
.expect("config should load");
let _ = config.features.set_enabled(Feature::Apps, true);
let cache_key = accessible_connectors_cache_key(&config, None);
let _ = config.features.set_enabled(Feature::Apps, /*enabled*/ true);
let cache_key = accessible_connectors_cache_key(&config, /*auth*/ None);
let tools = HashMap::from([
(
"mcp__codex_apps__calendar_list_events".to_string(),
@@ -253,7 +253,7 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
]);
let cached = with_accessible_connectors_cache_cleared(|| {
refresh_accessible_connectors_cache_from_mcp_tools(&config, None, &tools);
refresh_accessible_connectors_cache_from_mcp_tools(&config, /*auth*/ None, &tools);
read_cached_accessible_connectors(&cache_key).expect("cache should be populated")
});
@@ -367,8 +367,8 @@ fn app_tool_policy_uses_global_defaults_for_destructive_hints() {
Some(&apps_config),
Some("calendar"),
"events/create",
None,
Some(&annotations(Some(true), None)),
/*tool_title*/ None,
Some(&annotations(Some(true), /*open_world_hint*/ None)),
);
assert_eq!(
@@ -392,7 +392,7 @@ fn app_is_enabled_uses_default_for_unconfigured_apps() {
};
assert!(!app_is_enabled(&apps_config, Some("calendar")));
assert!(!app_is_enabled(&apps_config, None));
assert!(!app_is_enabled(&apps_config, /*connector_id*/ None));
}
#[test]
@@ -518,7 +518,13 @@ enabled = true
.await
.expect("config should build");
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
let policy = app_tool_policy(
&config,
Some("connector_123123"),
"events.list",
/*tool_title*/ None,
/*annotations*/ None,
);
assert_eq!(
policy,
AppToolPolicy {
@@ -555,7 +561,13 @@ async fn cloud_requirements_disable_connector_applies_without_user_apps_table()
.await
.expect("config should build");
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
let policy = app_tool_policy(
&config,
Some("connector_123123"),
"events.list",
/*tool_title*/ None,
/*annotations*/ None,
);
assert_eq!(
policy,
AppToolPolicy {
@@ -602,7 +614,13 @@ enabled = true
.expect("apps config"),
);
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
let policy = app_tool_policy(
&config,
Some("connector_123123"),
"events.list",
/*tool_title*/ None,
/*annotations*/ None,
);
assert_eq!(
policy,
AppToolPolicy {
@@ -637,7 +655,13 @@ async fn local_requirements_disable_connector_applies_without_user_apps_table()
ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements)
.expect("requirements stack");
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
let policy = app_tool_policy(
&config,
Some("connector_123123"),
"events.list",
/*tool_title*/ None,
/*annotations*/ None,
);
assert_eq!(
policy,
AppToolPolicy {
@@ -699,8 +723,10 @@ fn app_tool_policy_honors_default_app_enabled_false() {
Some(&apps_config),
Some("calendar"),
"events/list",
None,
Some(&annotations(None, None)),
/*tool_title*/ None,
Some(&annotations(
/*destructive_hint*/ None, /*open_world_hint*/ None,
)),
);
assert_eq!(
@@ -737,8 +763,10 @@ fn app_tool_policy_allows_per_app_enable_when_default_is_disabled() {
Some(&apps_config),
Some("calendar"),
"events/list",
None,
Some(&annotations(None, None)),
/*tool_title*/ None,
Some(&annotations(
/*destructive_hint*/ None, /*open_world_hint*/ None,
)),
);
assert_eq!(
@@ -779,7 +807,7 @@ fn app_tool_policy_per_tool_enabled_true_overrides_app_level_disable_flags() {
Some(&apps_config),
Some("calendar"),
"events/create",
None,
/*tool_title*/ None,
Some(&annotations(Some(true), Some(true))),
);
@@ -813,7 +841,7 @@ fn app_tool_policy_default_tools_enabled_true_overrides_app_level_tool_hints() {
Some(&apps_config),
Some("calendar"),
"events/create",
None,
/*tool_title*/ None,
Some(&annotations(Some(true), Some(true))),
);
@@ -847,8 +875,10 @@ fn app_tool_policy_default_tools_enabled_false_overrides_app_level_tool_hints()
Some(&apps_config),
Some("calendar"),
"events/list",
None,
Some(&annotations(None, None)),
/*tool_title*/ None,
Some(&annotations(
/*destructive_hint*/ None, /*open_world_hint*/ None,
)),
);
assert_eq!(
@@ -883,8 +913,10 @@ fn app_tool_policy_uses_default_tools_approval_mode() {
Some(&apps_config),
Some("calendar"),
"events/list",
None,
Some(&annotations(None, None)),
/*tool_title*/ None,
Some(&annotations(
/*destructive_hint*/ None, /*open_world_hint*/ None,
)),
);
assert_eq!(
@@ -51,7 +51,7 @@ fn inter_agent_assistant_msg(text: &str) -> ResponseItem {
AgentPath::root().join("worker").unwrap(),
Vec::new(),
text.to_string(),
true,
/*trigger_turn*/ true,
);
ResponseItem::Message {
id: None,
@@ -246,7 +246,8 @@ fn filters_non_api_messages() {
#[test]
fn non_last_reasoning_tokens_return_zero_when_no_user_messages() {
let history = create_history_with_items(vec![reasoning_with_encrypted_content(800)]);
let history =
create_history_with_items(vec![reasoning_with_encrypted_content(/*len*/ 800)]);
assert_eq!(history.get_non_last_reasoning_items_tokens(), 0);
}
@@ -254,11 +255,11 @@ fn non_last_reasoning_tokens_return_zero_when_no_user_messages() {
#[test]
fn non_last_reasoning_tokens_ignore_entries_after_last_user() {
let history = create_history_with_items(vec![
reasoning_with_encrypted_content(900),
reasoning_with_encrypted_content(/*len*/ 900),
user_msg("first"),
reasoning_with_encrypted_content(1_000),
reasoning_with_encrypted_content(/*len*/ 1_000),
user_msg("second"),
reasoning_with_encrypted_content(2_000),
reasoning_with_encrypted_content(/*len*/ 2_000),
]);
// first: (900 * 0.75 - 650) / 4 = 6.25 tokens
// second: (1000 * 0.75 - 650) / 4 = 25 tokens
@@ -330,7 +331,7 @@ fn drop_last_n_user_turns_treats_inter_agent_assistant_messages_as_instruction_t
inter_agent_reply,
]);
history.drop_last_n_user_turns(1);
history.drop_last_n_user_turns(/*num_turns*/ 1);
assert_eq!(history.raw_items(), &vec![first_turn, first_reply]);
}
@@ -352,7 +353,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() {
total_tokens: 100,
..Default::default()
},
None,
/*model_context_window*/ None,
);
let added_user = user_msg("new user message");
let added_tool_output = custom_tool_call_output("tool-tail", "new tool output");
@@ -362,7 +363,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() {
);
assert_eq!(
history.get_total_token_usage(true),
history.get_total_token_usage(/*server_reasoning_included*/ true),
100 + estimate_item_token_count(&added_user)
+ estimate_item_token_count(&added_tool_output)
);
@@ -606,7 +607,12 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
#[test]
fn get_history_for_prompt_drops_ghost_commits() {
let items = vec![ResponseItem::GhostSnapshot {
ghost_commit: GhostCommit::new("ghost-1".to_string(), None, Vec::new(), Vec::new()),
ghost_commit: GhostCommit::new(
"ghost-1".to_string(),
/*parent*/ None,
Vec::new(),
Vec::new(),
),
}];
let history = create_history_with_items(items);
let modalities = default_input_modalities();
@@ -792,7 +798,7 @@ fn drop_last_n_user_turns_preserves_prefix() {
let modalities = default_input_modalities();
let mut history = create_history_with_items(items);
history.drop_last_n_user_turns(1);
history.drop_last_n_user_turns(/*num_turns*/ 1);
assert_eq!(
history.for_prompt(&modalities),
vec![
@@ -809,7 +815,7 @@ fn drop_last_n_user_turns_preserves_prefix() {
user_msg("u2"),
assistant_msg("a2"),
]);
history.drop_last_n_user_turns(99);
history.drop_last_n_user_turns(/*num_turns*/ 99);
assert_eq!(
history.for_prompt(&modalities),
vec![assistant_msg("session prefix item")]
@@ -838,7 +844,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {
let modalities = default_input_modalities();
let mut history = create_history_with_items(items);
history.drop_last_n_user_turns(1);
history.drop_last_n_user_turns(/*num_turns*/ 1);
let expected_prefix_and_first_turn = vec![
user_input_text_msg("<environment_context>ctx</environment_context>"),
@@ -892,7 +898,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {
user_input_text_msg("turn 2 user"),
assistant_msg("turn 2 assistant"),
]);
history.drop_last_n_user_turns(2);
history.drop_last_n_user_turns(/*num_turns*/ 2);
assert_eq!(history.for_prompt(&modalities), expected_prefix_only);
let mut history = create_history_with_items(vec![
@@ -912,7 +918,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {
user_input_text_msg("turn 2 user"),
assistant_msg("turn 2 assistant"),
]);
history.drop_last_n_user_turns(3);
history.drop_last_n_user_turns(/*num_turns*/ 3);
assert_eq!(history.for_prompt(&modalities), expected_prefix_only);
}
@@ -935,7 +941,7 @@ fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() {
let mut history = create_history_with_items(items);
let reference_context_item = reference_context_item();
history.set_reference_context_item(Some(reference_context_item.clone()));
history.drop_last_n_user_turns(1);
history.drop_last_n_user_turns(/*num_turns*/ 1);
assert_eq!(
history.clone().for_prompt(&modalities),
@@ -973,7 +979,7 @@ fn drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_b
let modalities = default_input_modalities();
let mut history = create_history_with_items(items);
history.set_reference_context_item(Some(reference_context_item()));
history.drop_last_n_user_turns(1);
history.drop_last_n_user_turns(/*num_turns*/ 1);
assert_eq!(
history.clone().for_prompt(&modalities),
@@ -1165,7 +1171,7 @@ fn format_exec_output_truncates_large_error() {
let truncated = truncate_exec_output(&large_error);
assert_truncated_message_matches(&truncated, line, 36250);
assert_truncated_message_matches(&truncated, line, /*expected_removed*/ 36250);
assert_ne!(truncated, large_error);
}
@@ -1174,7 +1180,7 @@ fn format_exec_output_marks_byte_truncation_without_omitted_lines() {
let long_line = "a".repeat(EXEC_FORMAT_MAX_BYTES + 10000);
let truncated = truncate_exec_output(&long_line);
assert_ne!(truncated, long_line);
assert_truncated_message_matches(&truncated, "a", 2500);
assert_truncated_message_matches(&truncated, "a", /*expected_removed*/ 2500);
assert!(
!truncated.contains("omitted"),
"line omission marker should not appear when no lines were dropped: {truncated}"
@@ -1196,7 +1202,7 @@ fn format_exec_output_reports_omitted_lines_and_keeps_head_and_tail() {
.collect();
let truncated = truncate_exec_output(&content);
assert_truncated_message_matches(&truncated, "line-0-", 34_723);
assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 34_723);
assert!(
truncated.contains("line-0-"),
"expected head line to remain: {truncated}"
@@ -1219,7 +1225,7 @@ fn format_exec_output_prefers_line_marker_when_both_limits_exceeded() {
let truncated = truncate_exec_output(&content);
assert_truncated_message_matches(&truncated, "line-0-", 17_423);
assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 17_423);
}
#[cfg(not(debug_assertions))]
@@ -86,8 +86,8 @@ fn detects_hook_prompt_fragment_and_roundtrips_escaping() {
let ContentItem::InputText { text } = content_item else {
panic!("expected input text content item");
};
let parsed =
parse_visible_hook_prompt_message(None, content.as_slice()).expect("visible hook prompt");
let parsed = parse_visible_hook_prompt_message(/*id*/ None, content.as_slice())
.expect("visible hook prompt");
assert_eq!(
parsed.fragments,
vec![HookPromptFragment {
+48 -48
View File
@@ -20,8 +20,8 @@ fn serialize_workspace_write_environment_context() {
fake_shell(),
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
None,
None,
/*network*/ None,
/*subagents*/ None,
);
let expected = format!(
@@ -49,7 +49,7 @@ fn serialize_environment_context_with_network() {
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
Some(network),
None,
/*subagents*/ None,
);
let expected = format!(
@@ -73,12 +73,12 @@ fn serialize_environment_context_with_network() {
#[test]
fn serialize_read_only_environment_context() {
let context = EnvironmentContext::new(
None,
/*cwd*/ None,
fake_shell(),
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
None,
None,
/*network*/ None,
/*subagents*/ None,
);
let expected = r#"<environment_context>
@@ -93,12 +93,12 @@ fn serialize_read_only_environment_context() {
#[test]
fn serialize_external_sandbox_environment_context() {
let context = EnvironmentContext::new(
None,
/*cwd*/ None,
fake_shell(),
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
None,
None,
/*network*/ None,
/*subagents*/ None,
);
let expected = r#"<environment_context>
@@ -113,12 +113,12 @@ fn serialize_external_sandbox_environment_context() {
#[test]
fn serialize_external_sandbox_with_restricted_network_environment_context() {
let context = EnvironmentContext::new(
None,
/*cwd*/ None,
fake_shell(),
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
None,
None,
/*network*/ None,
/*subagents*/ None,
);
let expected = r#"<environment_context>
@@ -133,12 +133,12 @@ fn serialize_external_sandbox_with_restricted_network_environment_context() {
#[test]
fn serialize_full_access_environment_context() {
let context = EnvironmentContext::new(
None,
/*cwd*/ None,
fake_shell(),
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
None,
None,
/*network*/ None,
/*subagents*/ None,
);
let expected = r#"<environment_context>
@@ -155,18 +155,18 @@ fn equals_except_shell_compares_cwd() {
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
fake_shell(),
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
fake_shell(),
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
assert!(context1.equals_except_shell(&context2));
}
@@ -176,18 +176,18 @@ fn equals_except_shell_ignores_sandbox_policy() {
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
fake_shell(),
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
fake_shell(),
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
assert!(context1.equals_except_shell(&context2));
@@ -198,18 +198,18 @@ fn equals_except_shell_compares_cwd_differences() {
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo1")),
fake_shell(),
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo2")),
fake_shell(),
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
assert!(!context1.equals_except_shell(&context2));
@@ -224,10 +224,10 @@ fn equals_except_shell_ignores_shell() {
shell_path: "/bin/bash".into(),
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
},
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
@@ -236,10 +236,10 @@ fn equals_except_shell_ignores_shell() {
shell_path: "/bin/zsh".into(),
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
},
None,
None,
None,
None,
/*current_date*/ None,
/*timezone*/ None,
/*network*/ None,
/*subagents*/ None,
);
assert!(context1.equals_except_shell(&context2));
@@ -252,7 +252,7 @@ fn serialize_environment_context_with_subagents() {
fake_shell(),
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
None,
/*network*/ None,
Some("- agent-1: atlas\n- agent-2".to_string()),
);
+1 -1
View File
@@ -56,7 +56,7 @@ fn parses_user_message_with_text_and_two_images() {
#[test]
fn skips_local_image_label_text() {
let image_url = "data:image/png;base64,abc".to_string();
let label = codex_protocol::models::local_image_open_tag_text(1);
let label = codex_protocol::models::local_image_open_tag_text(/*label_number*/ 1);
let user_text = "Please review this image.".to_string();
let item = ResponseItem::Message {
+1 -1
View File
@@ -121,7 +121,7 @@ fn populate_env_inserts_thread_id() {
fn populate_env_omits_thread_id_when_missing() {
let vars = make_vars(&[("PATH", "/usr/bin")]);
let policy = ShellEnvironmentPolicy::default();
let result = populate_env(vars, &policy, None);
let result = populate_env(vars, &policy, /*thread_id*/ None);
let expected: HashMap<String, String> = hashmap! {
"PATH".to_string() => "/usr/bin".to_string(),
+13 -7
View File
@@ -115,7 +115,10 @@ async fn child_uses_parent_exec_policy_when_non_exec_policy_layers_differ() {
let mut child_config = parent_config.clone();
let mut layers: Vec<_> = child_config
.config_layer_stack
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.cloned()
.collect();
@@ -156,7 +159,10 @@ async fn child_does_not_use_parent_exec_policy_when_requirements_exec_policy_dif
child_config.config_layer_stack = ConfigLayerStack::new(
child_config
.config_layer_stack
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.cloned()
.collect(),
@@ -291,7 +297,7 @@ async fn merges_requirements_exec_policy_network_rules() -> anyhow::Result<()> {
"blocked.example.com",
codex_execpolicy::NetworkRuleProtocol::Https,
Decision::Forbidden,
None,
/*justification*/ None,
)?;
let requirements = ConfigRequirements {
@@ -338,7 +344,7 @@ host_executable(name = "git", paths = ["{git_path_literal}"])
"blocked.example.com",
codex_execpolicy::NetworkRuleProtocol::Https,
Decision::Forbidden,
None,
/*justification*/ None,
)?;
let requirements = ConfigRequirements {
@@ -805,7 +811,7 @@ fn unmatched_granular_policy_still_prompts_for_restricted_sandbox_escalation() {
&read_only_file_system_sandbox_policy(),
&command,
SandboxPermissions::RequireEscalated,
false,
/*used_complex_parsing*/ false,
)
);
}
@@ -823,7 +829,7 @@ fn unmatched_on_request_uses_split_filesystem_policy_for_escalation_prompts() {
&restricted_file_system_policy,
&command,
SandboxPermissions::RequireEscalated,
false,
/*used_complex_parsing*/ false,
)
);
}
@@ -1348,7 +1354,7 @@ fn derive_requested_execpolicy_amendment_for_test(
fn derive_requested_execpolicy_amendment_returns_none_for_missing_prefix_rule() {
assert_eq!(
None,
derive_requested_execpolicy_amendment_for_test(None, &[])
derive_requested_execpolicy_amendment_for_test(/*prefix_rule*/ None, &[])
);
}
+30 -20
View File
@@ -24,7 +24,7 @@ fn make_exec_output(
#[test]
fn sandbox_detection_requires_keywords() {
let output = make_exec_output(1, "", "", "");
let output = make_exec_output(/*exit_code*/ 1, "", "", "");
assert!(!is_likely_sandbox_denied(
SandboxType::LinuxSeccomp,
&output
@@ -33,13 +33,13 @@ fn sandbox_detection_requires_keywords() {
#[test]
fn sandbox_detection_identifies_keyword_in_stderr() {
let output = make_exec_output(1, "", "Operation not permitted", "");
let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");
assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output));
}
#[test]
fn sandbox_detection_respects_quick_reject_exit_codes() {
let output = make_exec_output(127, "", "command not found", "");
let output = make_exec_output(/*exit_code*/ 127, "", "command not found", "");
assert!(!is_likely_sandbox_denied(
SandboxType::LinuxSeccomp,
&output
@@ -48,14 +48,14 @@ fn sandbox_detection_respects_quick_reject_exit_codes() {
#[test]
fn sandbox_detection_ignores_non_sandbox_mode() {
let output = make_exec_output(1, "", "Operation not permitted", "");
let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");
assert!(!is_likely_sandbox_denied(SandboxType::None, &output));
}
#[test]
fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() {
let output = make_exec_output(
0,
/*exit_code*/ 0,
"",
"",
r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","reason":"not_allowed","source":"decider","protocol":"http","host":"google.com","port":80}"#,
@@ -66,7 +66,7 @@ fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() {
#[test]
fn sandbox_detection_uses_aggregated_output() {
let output = make_exec_output(
101,
/*exit_code*/ 101,
"",
"",
"cargo failed: Read-only file system when writing target",
@@ -80,7 +80,7 @@ fn sandbox_detection_uses_aggregated_output() {
#[test]
fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() {
let output = make_exec_output(
0,
/*exit_code*/ 0,
"",
"",
r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","source":"decider","protocol":"http","host":"google.com","port":80}"#,
@@ -100,9 +100,14 @@ async fn read_output_limits_retained_bytes_for_shell_capture() {
writer.write_all(&bytes).await.expect("write");
});
let out = read_output(reader, None, false, Some(EXEC_OUTPUT_MAX_BYTES))
.await
.expect("read");
let out = read_output(
reader,
/*stream*/ None,
/*is_stderr*/ false,
Some(EXEC_OUTPUT_MAX_BYTES),
)
.await
.expect("read");
assert_eq!(out.text.len(), EXEC_OUTPUT_MAX_BYTES);
}
@@ -196,7 +201,11 @@ async fn read_output_retains_all_bytes_for_full_buffer_capture() {
writer.write_all(&bytes).await.expect("write");
});
let out = read_output(reader, None, false, None).await.expect("read");
let out = read_output(
reader, /*stream*/ None, /*is_stderr*/ false, /*max_bytes*/ None,
)
.await
.expect("read");
assert_eq!(out.text.len(), expected_len);
}
@@ -211,7 +220,7 @@ fn aggregate_output_keeps_all_bytes_when_uncapped() {
truncated_after_lines: None,
};
let aggregated = aggregate_output(&stdout, &stderr, None);
let aggregated = aggregate_output(&stdout, &stderr, /*max_bytes*/ None);
assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES * 2);
assert_eq!(
@@ -362,8 +371,8 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
NetworkSandboxPolicy::Enabled,
cwd.as_path(),
&None,
false,
None,
/*use_legacy_landlock*/ false,
/*stdout_stream*/ None,
)
.await?;
@@ -675,14 +684,15 @@ fn windows_elevated_rejects_split_write_read_carveouts() {
#[test]
fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions() {
let expected = codex_sandboxing::get_platform_sandbox(false).unwrap_or(SandboxType::None);
let expected = codex_sandboxing::get_platform_sandbox(/*windows_sandbox_enabled*/ false)
.unwrap_or(SandboxType::None);
assert_eq!(
select_process_exec_tool_sandbox_type(
&FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Restricted,
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
false,
/*enforce_managed_network*/ false,
),
expected
);
@@ -736,8 +746,8 @@ async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()>
&FileSystemSandboxPolicy::from(&SandboxPolicy::new_read_only_policy()),
None,
NetworkSandboxPolicy::Restricted,
None,
None,
/*stdout_stream*/ None,
/*after_spawn*/ None,
)
.await?;
assert!(output.timed_out);
@@ -798,8 +808,8 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
NetworkSandboxPolicy::Enabled,
cwd.as_path(),
&None,
false,
None,
/*use_legacy_landlock*/ false,
/*stdout_stream*/ None,
)
.await;
let output = match result {
@@ -390,7 +390,7 @@ fn import_skills_returns_only_new_skill_directory_count() {
fs::create_dir_all(agents_skills.join("skill-a")).expect("create existing target");
let copied_count = service_for_paths(claude_home, codex_home)
.import_skills(None)
.import_skills(/*cwd*/ None)
.expect("import skills");
assert_eq!(copied_count, 1);
+17 -15
View File
@@ -115,10 +115,10 @@ fn is_mutating_event_filters_non_mutating_event_kinds() {
fn register_dedupes_by_path_and_scope() {
let watcher = Arc::new(FileWatcher::noop());
let (subscriber, _rx) = watcher.add_subscriber();
let _first = subscriber.register_path(path("/tmp/skills"), false);
let _second = subscriber.register_path(path("/tmp/skills"), false);
let _third = subscriber.register_path(path("/tmp/skills"), true);
let _fourth = subscriber.register_path(path("/tmp/other-skills"), true);
let _first = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false);
let _second = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false);
let _third = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
let _fourth = subscriber.register_path(path("/tmp/other-skills"), /*recursive*/ true);
assert_eq!(
watcher.watch_counts_for_test(&path("/tmp/skills")),
@@ -134,7 +134,7 @@ fn register_dedupes_by_path_and_scope() {
fn watch_registration_drop_unregisters_paths() {
let watcher = Arc::new(FileWatcher::noop());
let (subscriber, _rx) = watcher.add_subscriber();
let registration = subscriber.register_path(path("/tmp/skills"), true);
let registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
drop(registration);
@@ -146,7 +146,7 @@ fn subscriber_drop_unregisters_paths() {
let watcher = Arc::new(FileWatcher::noop());
let registration = {
let (subscriber, _rx) = watcher.add_subscriber();
subscriber.register_path(path("/tmp/skills"), true)
subscriber.register_path(path("/tmp/skills"), /*recursive*/ true)
};
assert_eq!(watcher.watch_counts_for_test(&path("/tmp/skills")), None);
@@ -174,8 +174,8 @@ fn recursive_registration_downgrades_to_non_recursive_after_drop() {
let watcher = Arc::new(FileWatcher::new().expect("watcher"));
let (subscriber, _rx) = watcher.add_subscriber();
let non_recursive = subscriber.register_path(root.clone(), false);
let recursive = subscriber.register_path(root.clone(), true);
let non_recursive = subscriber.register_path(root.clone(), /*recursive*/ false);
let recursive = subscriber.register_path(root.clone(), /*recursive*/ true);
{
let inner = watcher.inner.as_ref().expect("watcher inner");
@@ -209,7 +209,7 @@ fn unregister_holds_state_lock_until_unwatch_finishes() {
let watcher = Arc::new(FileWatcher::new().expect("watcher"));
let (unregister_subscriber, _unregister_rx) = watcher.add_subscriber();
let (register_subscriber, _register_rx) = watcher.add_subscriber();
let registration = unregister_subscriber.register_path(root.clone(), true);
let registration = unregister_subscriber.register_path(root.clone(), /*recursive*/ true);
let inner = watcher.inner.as_ref().expect("watcher inner");
let inner_guard = inner.lock().expect("inner lock");
@@ -229,7 +229,8 @@ fn unregister_holds_state_lock_until_unwatch_finishes() {
let register_root = root.clone();
let register_thread = std::thread::spawn(move || {
let registration = register_subscriber.register_path(register_root, false);
let registration =
register_subscriber.register_path(register_root, /*recursive*/ false);
(register_subscriber, registration)
});
@@ -257,8 +258,8 @@ async fn matching_subscribers_are_notified() {
let watcher = Arc::new(FileWatcher::noop());
let (skills_subscriber, skills_rx) = watcher.add_subscriber();
let (plugins_subscriber, plugins_rx) = watcher.add_subscriber();
let _skills = skills_subscriber.register_path(path("/tmp/skills"), true);
let _plugins = plugins_subscriber.register_path(path("/tmp/plugins"), true);
let _skills = skills_subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
let _plugins = plugins_subscriber.register_path(path("/tmp/plugins"), /*recursive*/ true);
let mut skills_rx = ThrottledWatchReceiver::new(skills_rx, TEST_THROTTLE_INTERVAL);
let mut plugins_rx = ThrottledWatchReceiver::new(plugins_rx, TEST_THROTTLE_INTERVAL);
@@ -285,7 +286,7 @@ async fn matching_subscribers_are_notified() {
async fn non_recursive_watch_ignores_grandchildren() {
let watcher = Arc::new(FileWatcher::noop());
let (subscriber, rx) = watcher.add_subscriber();
let _registration = subscriber.register_path(path("/tmp/skills"), false);
let _registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false);
let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL);
watcher
@@ -300,7 +301,8 @@ async fn non_recursive_watch_ignores_grandchildren() {
async fn ancestor_events_notify_child_watches() {
let watcher = Arc::new(FileWatcher::noop());
let (subscriber, rx) = watcher.add_subscriber();
let _registration = subscriber.register_path(path("/tmp/skills/rust/SKILL.md"), false);
let _registration =
subscriber.register_path(path("/tmp/skills/rust/SKILL.md"), /*recursive*/ false);
let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL);
watcher.send_paths_for_test(vec![path("/tmp/skills")]).await;
@@ -321,7 +323,7 @@ async fn ancestor_events_notify_child_watches() {
async fn spawn_event_loop_filters_non_mutating_events() {
let watcher = Arc::new(FileWatcher::noop());
let (subscriber, rx) = watcher.add_subscriber();
let _registration = subscriber.register_path(path("/tmp/skills"), true);
let _registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL);
let (raw_tx, raw_rx) = mpsc::unbounded_channel();
watcher.spawn_event_loop_for_test(raw_rx);
+2 -2
View File
@@ -72,7 +72,7 @@ async fn create_test_git_repo(temp_dir: &TempDir) -> PathBuf {
#[tokio::test]
async fn test_recent_commits_non_git_directory_returns_empty() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let entries = recent_commits(temp_dir.path(), 10).await;
let entries = recent_commits(temp_dir.path(), /*limit*/ 10).await;
assert!(entries.is_empty(), "expected no commits outside a git repo");
}
@@ -133,7 +133,7 @@ async fn test_recent_commits_orders_and_limits() {
.expect("git commit 3");
// Request the latest 3 commits; should be our three changes in reverse time order.
let entries = recent_commits(&repo_path, 3).await;
let entries = recent_commits(&repo_path, /*limit*/ 3).await;
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].subject, "third change");
assert_eq!(entries[1].subject, "second change");
+12 -8
View File
@@ -746,9 +746,13 @@ mod tests {
#[test]
fn guardian_review_session_config_change_invalidates_cached_session() {
let parent_config = crate::config::test_config();
let cached_spawn_config =
build_guardian_review_session_config(&parent_config, None, "active-model", None)
.expect("cached guardian config");
let cached_spawn_config = build_guardian_review_session_config(
&parent_config,
/*live_network_config*/ None,
"active-model",
/*reasoning_effort*/ None,
)
.expect("cached guardian config");
let cached_reuse_key =
GuardianReviewSessionReuseKey::from_spawn_config(&cached_spawn_config);
@@ -757,9 +761,9 @@ mod tests {
Some("https://guardian.example.invalid/v1".to_string());
let next_spawn_config = build_guardian_review_session_config(
&changed_parent_config,
None,
/*live_network_config*/ None,
"active-model",
None,
/*reasoning_effort*/ None,
)
.expect("next guardian config");
let next_reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(&next_spawn_config);
@@ -775,7 +779,7 @@ mod tests {
async fn run_before_review_deadline_times_out_before_future_completes() {
let outcome = run_before_review_deadline(
tokio::time::Instant::now() + Duration::from_millis(10),
None,
/*external_cancel*/ None,
async {
tokio::time::sleep(Duration::from_millis(50)).await;
},
@@ -816,7 +820,7 @@ mod tests {
let outcome = run_before_review_deadline_with_cancel(
tokio::time::Instant::now() + Duration::from_millis(10),
None,
/*external_cancel*/ None,
&cancel_token,
async {
tokio::time::sleep(Duration::from_millis(50)).await;
@@ -862,7 +866,7 @@ mod tests {
let outcome = run_before_review_deadline_with_cancel(
tokio::time::Instant::now() + Duration::from_secs(1),
None,
/*external_cancel*/ None,
&cancel_token,
async { 42usize },
)
+46 -26
View File
@@ -251,7 +251,7 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
fn guardian_truncate_text_keeps_prefix_suffix_and_xml_marker() {
let content = "prefix ".repeat(200) + &" suffix".repeat(200);
let truncated = guardian_truncate_text(&content, 20);
let truncated = guardian_truncate_text(&content, /*token_cap*/ 20);
assert!(truncated.starts_with("prefix"));
assert!(truncated.contains("<truncated omitted_approx_tokens=\""));
@@ -392,7 +392,7 @@ async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
.to_string(),
},
None,
/*retry_reason*/ None,
cancel_token,
)
.await;
@@ -556,7 +556,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
Arc::clone(&turn),
prompt,
guardian_output_schema(),
None,
/*external_cancel*/ None,
)
.await;
let GuardianReviewOutcome::Completed(Ok(assessment)) = outcome else {
@@ -634,7 +634,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
Arc::clone(&turn),
first_prompt,
guardian_output_schema(),
None,
/*external_cancel*/ None,
)
.await;
let second_prompt = build_guardian_prompt_items(
@@ -659,7 +659,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
Arc::clone(&turn),
second_prompt,
guardian_output_schema(),
None,
/*external_cancel*/ None,
)
.await;
@@ -772,7 +772,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
additional_permissions: None,
justification: Some("Need to push the reviewed docs fix.".to_string()),
},
None,
/*retry_reason*/ None,
)
.await;
@@ -884,7 +884,7 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a
justification: Some("Inspect repo state before proceeding.".to_string()),
};
assert_eq!(
review_approval_request(&session, &turn, initial_request, None).await,
review_approval_request(&session, &turn, initial_request, /*retry_reason*/ None).await,
ReviewDecision::Approved
);
@@ -988,7 +988,7 @@ fn guardian_review_session_config_preserves_parent_network_proxy() {
let guardian_config = build_guardian_review_session_config_for_test(
&parent_config,
None,
/*live_network_config*/ None,
"parent-active-model",
Some(codex_protocol::openai_models::ReasoningEffort::Low),
)
@@ -1019,9 +1019,13 @@ fn guardian_review_session_config_overrides_parent_developer_instructions() {
parent_config.developer_instructions =
Some("parent or managed config should not replace guardian policy".to_string());
let guardian_config =
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
.expect("guardian config");
let guardian_config = build_guardian_review_session_config_for_test(
&parent_config,
/*live_network_config*/ None,
"active-model",
/*reasoning_effort*/ None,
)
.expect("guardian config");
assert_eq!(
guardian_config.developer_instructions,
@@ -1040,7 +1044,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
parent_config.permissions.network = Some(
NetworkProxySpec::from_config_and_constraints(
parent_network,
None,
/*requirements*/ None,
parent_config.permissions.sandbox_policy.get(),
)
.expect("parent network proxy spec"),
@@ -1056,7 +1060,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
&parent_config,
Some(live_network.clone()),
"active-model",
None,
/*reasoning_effort*/ None,
)
.expect("guardian config");
@@ -1065,7 +1069,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
Some(
NetworkProxySpec::from_config_and_constraints(
live_network,
None,
/*requirements*/ None,
&SandboxPolicy::new_read_only_policy(),
)
.expect("live network proxy spec")
@@ -1087,9 +1091,13 @@ fn guardian_review_session_config_rejects_pinned_collab_feature() {
)
.expect("managed features");
let err =
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
.expect_err("guardian config should fail when collab is pinned on");
let err = build_guardian_review_session_config_for_test(
&parent_config,
/*live_network_config*/ None,
"active-model",
/*reasoning_effort*/ None,
)
.expect_err("guardian config should fail when collab is pinned on");
assert!(
err.to_string()
@@ -1102,9 +1110,13 @@ fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_
let mut parent_config = test_config();
parent_config.model = Some("configured-model".to_string());
let guardian_config =
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
.expect("guardian config");
let guardian_config = build_guardian_review_session_config_for_test(
&parent_config,
/*live_network_config*/ None,
"active-model",
/*reasoning_effort*/ None,
)
.expect("guardian config");
assert_eq!(guardian_config.model, Some("active-model".to_string()));
}
@@ -1135,9 +1147,13 @@ fn guardian_review_session_config_uses_requirements_guardian_override() {
)
.expect("load config");
let guardian_config =
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
.expect("guardian config");
let guardian_config = build_guardian_review_session_config_for_test(
&parent_config,
/*live_network_config*/ None,
"active-model",
/*reasoning_effort*/ None,
)
.expect("guardian config");
assert_eq!(
guardian_config.developer_instructions,
@@ -1163,9 +1179,13 @@ fn guardian_review_session_config_uses_default_guardian_policy_without_requireme
)
.expect("load config");
let guardian_config =
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
.expect("guardian config");
let guardian_config = build_guardian_review_session_config_for_test(
&parent_config,
/*live_network_config*/ None,
"active-model",
/*reasoning_effort*/ None,
)
.expect("guardian config");
assert_eq!(
guardian_config.developer_instructions,
+15 -4
View File
@@ -209,7 +209,7 @@ mod tests {
#[test]
fn resolve_oauth_scopes_prefers_configured_over_discovered() {
let resolved = resolve_oauth_scopes(
None,
/*explicit_scopes*/ None,
Some(vec!["configured".to_string()]),
Some(vec!["discovered".to_string()]),
);
@@ -225,7 +225,11 @@ mod tests {
#[test]
fn resolve_oauth_scopes_uses_discovered_when_needed() {
let resolved = resolve_oauth_scopes(None, None, Some(vec!["discovered".to_string()]));
let resolved = resolve_oauth_scopes(
/*explicit_scopes*/ None,
/*configured_scopes*/ None,
Some(vec!["discovered".to_string()]),
);
assert_eq!(
resolved,
@@ -238,7 +242,11 @@ mod tests {
#[test]
fn resolve_oauth_scopes_preserves_explicitly_empty_configured_scopes() {
let resolved = resolve_oauth_scopes(None, Some(Vec::new()), Some(vec!["ignored".into()]));
let resolved = resolve_oauth_scopes(
/*explicit_scopes*/ None,
Some(Vec::new()),
Some(vec!["ignored".into()]),
);
assert_eq!(
resolved,
@@ -251,7 +259,10 @@ mod tests {
#[test]
fn resolve_oauth_scopes_falls_back_to_empty() {
let resolved = resolve_oauth_scopes(None, None, None);
let resolved = resolve_oauth_scopes(
/*explicit_scopes*/ None, /*configured_scopes*/ None,
/*discovered_scopes*/ None,
);
assert_eq!(
resolved,
+10 -3
View File
@@ -167,7 +167,12 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() {
let mut config = crate::config::test_config();
config.chatgpt_base_url = "https://chatgpt.com".to_string();
let mut servers = with_codex_apps_mcp(HashMap::new(), false, None, &config);
let mut servers = with_codex_apps_mcp(
HashMap::new(),
/*connectors_enabled*/ false,
/*auth*/ None,
&config,
);
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
config
@@ -175,7 +180,9 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() {
.enable(Feature::Apps)
.expect("test config should allow apps");
servers = with_codex_apps_mcp(servers, true, None, &config);
servers = with_codex_apps_mcp(
servers, /*connectors_enabled*/ true, /*auth*/ None, &config,
);
let server = servers
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("codex apps should be present when apps is enabled");
@@ -252,7 +259,7 @@ async fn effective_mcp_servers_include_plugins_without_overriding_user_config()
.expect("test config should accept MCP servers");
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
let effective = mcp_manager.effective_servers(&config, None);
let effective = mcp_manager.effective_servers(&config, /*auth*/ None);
let sample = effective.get("sample").expect("user server should exist");
let docs = effective.get("docs").expect("plugin server should exist");
@@ -562,7 +562,7 @@ fn mcp_init_error_display_prompts_for_login_when_auth_required() {
let server_name = "example";
let err: StartupOutcomeError = anyhow::anyhow!("Auth required for server").into();
let display = mcp_init_error_display(server_name, None, &err);
let display = mcp_init_error_display(server_name, /*entry*/ None, &err);
let expected = format!(
"The {server_name} MCP server is not logged in. Run `codex mcp login {server_name}`."
@@ -609,7 +609,7 @@ fn mcp_init_error_display_includes_startup_timeout_hint() {
let server_name = "slow";
let err: StartupOutcomeError = anyhow::anyhow!("request timed out").into();
let display = mcp_init_error_display(server_name, None, &err);
let display = mcp_init_error_display(server_name, /*entry*/ None, &err);
assert_eq!(
"MCP client for `slow` timed out after 10 seconds. Add or adjust `startup_timeout_sec` in your config.toml:\n[mcp_servers.slow]\nstartup_timeout_sec = XX",
@@ -330,7 +330,7 @@ mod tests {
&templates,
"codex_apps",
Some("github"),
None,
/*connector_name*/ None,
Some("add_comment"),
Some(&json!({})),
);
@@ -361,7 +361,7 @@ mod tests {
&templates,
"codex_apps",
Some("calendar"),
None,
/*connector_name*/ None,
Some("create_event"),
Some(&json!({})),
),
+98 -44
View File
@@ -72,13 +72,13 @@ fn prompt_options(
#[test]
fn approval_required_when_read_only_false_and_destructive() {
let annotations = annotations(Some(false), Some(true), None);
let annotations = annotations(Some(false), Some(true), /*open_world*/ None);
assert_eq!(requires_mcp_tool_approval(Some(&annotations)), true);
}
#[test]
fn approval_required_when_read_only_false_and_open_world() {
let annotations = annotations(Some(false), None, Some(true));
let annotations = annotations(Some(false), /*destructive*/ None, Some(true));
assert_eq!(requires_mcp_tool_approval(Some(&annotations)), true);
}
@@ -90,12 +90,16 @@ fn approval_required_when_destructive_even_if_read_only_true() {
#[test]
fn approval_required_when_annotations_are_absent() {
assert_eq!(requires_mcp_tool_approval(None), true);
assert_eq!(requires_mcp_tool_approval(/*annotations*/ None), true);
}
#[test]
fn approval_not_required_when_read_only_and_other_hints_are_absent() {
let annotations = annotations(Some(true), None, None);
let annotations = annotations(
Some(true),
/*destructive*/ None,
/*open_world*/ None,
);
assert_eq!(requires_mcp_tool_approval(Some(&annotations)), false);
}
@@ -187,7 +191,9 @@ async fn approval_elicitation_request_uses_message_override_and_preserves_tool_p
CODEX_APPS_MCP_SERVER_NAME,
"create_event",
Some("Calendar"),
prompt_options(true, true),
prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
),
Some("Allow Calendar to create an event?"),
);
@@ -221,7 +227,9 @@ async fn approval_elicitation_request_uses_message_override_and_preserves_tool_p
]),
question,
message_override: Some("Allow Calendar to create an event?"),
prompt_options: prompt_options(true, true),
prompt_options: prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
),
},
);
@@ -279,9 +287,11 @@ fn custom_mcp_tool_question_mentions_server_name() {
"q".to_string(),
"custom_server",
"run_action",
None,
prompt_options(false, false),
None,
/*connector_name*/ None,
prompt_options(
/*allow_session_remember*/ false, /*allow_persistent_approval*/ false,
),
/*question_override*/ None,
);
assert_eq!(question.header, "Approve app tool call?");
@@ -305,9 +315,11 @@ fn codex_apps_tool_question_uses_fallback_app_label() {
"q".to_string(),
CODEX_APPS_MCP_SERVER_NAME,
"run_action",
None,
prompt_options(true, true),
None,
/*connector_name*/ None,
prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
),
/*question_override*/ None,
);
assert_eq!(
@@ -323,8 +335,10 @@ fn trusted_codex_apps_tool_question_offers_always_allow() {
CODEX_APPS_MCP_SERVER_NAME,
"run_action",
Some("Calendar"),
prompt_options(true, true),
None,
prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
),
/*question_override*/ None,
);
let options = question.options.expect("options");
@@ -363,8 +377,12 @@ fn codex_apps_tool_question_without_elicitation_omits_always_allow() {
CODEX_APPS_MCP_SERVER_NAME,
"run_action",
Some("Calendar"),
mcp_tool_approval_prompt_options(Some(&session_key), Some(&persistent_key), false),
None,
mcp_tool_approval_prompt_options(
Some(&session_key),
Some(&persistent_key),
/*tool_call_mcp_elicitation_enabled*/ false,
),
/*question_override*/ None,
);
assert_eq!(
@@ -388,9 +406,11 @@ fn custom_mcp_tool_question_offers_session_remember_and_always_allow() {
"q".to_string(),
"custom_server",
"run_action",
None,
prompt_options(true, true),
None,
/*connector_name*/ None,
prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
),
/*question_override*/ None,
);
assert_eq!(
@@ -423,11 +443,15 @@ fn custom_servers_support_session_and_persistent_approval() {
};
assert_eq!(
session_mcp_tool_approval_key(&invocation, None, AppToolApproval::Auto),
session_mcp_tool_approval_key(&invocation, /*metadata*/ None, AppToolApproval::Auto),
Some(expected.clone())
);
assert_eq!(
persistent_mcp_tool_approval_key(&invocation, None, AppToolApproval::Auto),
persistent_mcp_tool_approval_key(
&invocation,
/*metadata*/ None,
AppToolApproval::Auto
),
Some(expected)
);
}
@@ -439,7 +463,13 @@ fn codex_apps_connectors_support_persistent_approval() {
tool: "calendar/list_events".to_string(),
arguments: None,
};
let metadata = approval_metadata(Some("calendar"), Some("Calendar"), None, None, None);
let metadata = approval_metadata(
Some("calendar"),
Some("Calendar"),
/*connector_description*/ None,
/*tool_title*/ None,
/*tool_description*/ None,
);
let expected = McpToolApprovalKey {
server: CODEX_APPS_MCP_SERVER_NAME.to_string(),
connector_id: Some("calendar".to_string()),
@@ -475,7 +505,8 @@ fn sanitize_mcp_tool_result_for_model_rewrites_image_content() {
meta: None,
});
let got = sanitize_mcp_tool_result_for_model(false, result).expect("sanitized result");
let got = sanitize_mcp_tool_result_for_model(/*supports_image_input*/ false, result)
.expect("sanitized result");
assert_eq!(
got.content,
@@ -505,8 +536,11 @@ fn sanitize_mcp_tool_result_for_model_preserves_image_when_supported() {
meta: Some(serde_json::json!({"k": "v"})),
};
let got =
sanitize_mcp_tool_result_for_model(true, Ok(original.clone())).expect("unsanitized result");
let got = sanitize_mcp_tool_result_for_model(
/*supports_image_input*/ true,
Ok(original.clone()),
)
.expect("unsanitized result");
assert_eq!(got, original);
}
@@ -606,10 +640,12 @@ fn approval_elicitation_meta_marks_tool_approvals() {
assert_eq!(
build_mcp_tool_approval_elicitation_meta(
"custom_server",
None,
None,
None,
prompt_options(false, false),
/*metadata*/ None,
/*tool_params*/ None,
/*tool_params_display*/ None,
prompt_options(
/*allow_session_remember*/ false, /*allow_persistent_approval*/ false
),
),
Some(serde_json::json!({
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
@@ -623,15 +659,17 @@ fn approval_elicitation_meta_merges_session_and_always_persist_for_custom_server
build_mcp_tool_approval_elicitation_meta(
"custom_server",
Some(&approval_metadata(
None,
None,
None,
/*connector_id*/ None,
/*connector_name*/ None,
/*connector_description*/ None,
Some("Run Action"),
Some("Runs the selected action."),
)),
Some(&serde_json::json!({"id": 1})),
None,
prompt_options(true, true),
/*tool_params_display*/ None,
prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true
),
),
Some(serde_json::json!({
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
@@ -742,11 +780,11 @@ fn prepare_arc_request_action_serializes_mcp_tool_call_shape() {
let action = prepare_arc_request_action(
&invocation,
Some(&approval_metadata(
None,
/*connector_id*/ None,
Some("Playwright"),
None,
/*connector_description*/ None,
Some("Navigate"),
None,
/*tool_description*/ None,
)),
);
@@ -796,8 +834,10 @@ fn approval_elicitation_meta_includes_connector_source_for_codex_apps() {
Some(&serde_json::json!({
"calendar_id": "primary",
})),
None,
prompt_options(false, false),
/*tool_params_display*/ None,
prompt_options(
/*allow_session_remember*/ false, /*allow_persistent_approval*/ false
),
),
Some(serde_json::json!({
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
@@ -829,8 +869,10 @@ fn approval_elicitation_meta_merges_session_and_always_persist_with_connector_so
Some(&serde_json::json!({
"calendar_id": "primary",
})),
None,
prompt_options(true, true),
/*tool_params_display*/ None,
prompt_options(
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true
),
),
Some(serde_json::json!({
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
@@ -1170,7 +1212,11 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() {
arguments: None,
};
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(true), None, None)),
annotations: Some(annotations(
Some(true),
/*destructive*/ None,
/*open_world*/ None,
)),
connector_id: None,
connector_name: None,
connector_description: None,
@@ -1233,7 +1279,11 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() {
arguments: None,
};
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(true), None, None)),
annotations: Some(annotations(
Some(true),
/*destructive*/ None,
/*open_world*/ None,
)),
connector_id: None,
connector_name: None,
connector_description: None,
@@ -1268,7 +1318,11 @@ async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval
arguments: None,
};
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(true), None, None)),
annotations: Some(annotations(
Some(true),
/*destructive*/ None,
/*open_world*/ None,
)),
connector_id: None,
connector_name: None,
connector_description: None,
+1 -1
View File
@@ -29,7 +29,7 @@ fn fixed_thread_id() -> ThreadId {
#[test]
fn rollout_summary_file_stem_uses_uuid_timestamp_and_hash_when_slug_missing() {
let thread_id = fixed_thread_id();
let memory = stage1_output_with_slug(thread_id, None);
let memory = stage1_output_with_slug(thread_id, /*rollout_slug*/ None);
assert_eq!(rollout_summary_file_stem(&memory), FIXED_PREFIX);
assert_eq!(
+32 -26
View File
@@ -526,8 +526,8 @@ mod phase2 {
thread_id,
self.session.conversation_id,
source_updated_at,
3_600,
64,
/*lease_seconds*/ 3_600,
/*max_running_jobs*/ 64,
)
.await
.expect("claim stage-1 job");
@@ -543,7 +543,7 @@ mod phase2 {
source_updated_at,
"raw memory",
"rollout summary",
None,
/*rollout_slug*/ None,
)
.await
.expect("mark stage-1 success"),
@@ -571,24 +571,24 @@ mod phase2 {
#[test]
fn completion_watermark_never_regresses_below_claimed_input_watermark() {
let stage1_output = stage1_output_with_source_updated_at(123);
let stage1_output = stage1_output_with_source_updated_at(/*source_updated_at*/ 123);
let completion = phase2::get_watermark(1_000, &[stage1_output]);
let completion = phase2::get_watermark(/*claimed_watermark*/ 1_000, &[stage1_output]);
pretty_assertions::assert_eq!(completion, 1_000);
}
#[test]
fn completion_watermark_uses_claimed_watermark_when_there_are_no_memories() {
let completion = phase2::get_watermark(777, &[]);
let completion = phase2::get_watermark(/*claimed_watermark*/ 777, &[]);
pretty_assertions::assert_eq!(completion, 777);
}
#[test]
fn completion_watermark_uses_latest_memory_timestamp_when_it_is_newer() {
let older = stage1_output_with_source_updated_at(123);
let newer = stage1_output_with_source_updated_at(456);
let older = stage1_output_with_source_updated_at(/*source_updated_at*/ 123);
let newer = stage1_output_with_source_updated_at(/*source_updated_at*/ 456);
let completion = phase2::get_watermark(200, &[older, newer]);
let completion = phase2::get_watermark(/*claimed_watermark*/ 200, &[older, newer]);
pretty_assertions::assert_eq!(completion, 456);
}
@@ -608,12 +608,12 @@ mod phase2 {
let harness = DispatchHarness::new().await;
harness
.state_db
.enqueue_global_consolidation(123)
.enqueue_global_consolidation(/*input_watermark*/ 123)
.await
.expect("enqueue global consolidation");
let claimed = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim running global lock");
assert!(
@@ -625,7 +625,7 @@ mod phase2 {
let running_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim while lock is still running");
pretty_assertions::assert_eq!(running_claim, Phase2JobClaimOutcome::SkippedRunning);
@@ -641,7 +641,7 @@ mod phase2 {
let stale_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 0)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 0)
.await
.expect("claim stale global lock");
assert!(
@@ -653,7 +653,7 @@ mod phase2 {
let post_dispatch_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim after stale lock dispatch");
assert!(
@@ -759,7 +759,7 @@ mod phase2 {
harness
.state_db
.enqueue_global_consolidation(999)
.enqueue_global_consolidation(/*input_watermark*/ 999)
.await
.expect("enqueue global consolidation");
@@ -801,7 +801,7 @@ mod phase2 {
);
let next_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim global job after empty consolidation success");
pretty_assertions::assert_eq!(next_claim, Phase2JobClaimOutcome::SkippedNotDirty);
@@ -817,7 +817,7 @@ mod phase2 {
let harness = DispatchHarness::new().await;
harness
.state_db
.enqueue_global_consolidation(99)
.enqueue_global_consolidation(/*input_watermark*/ 99)
.await
.expect("enqueue global consolidation");
let mut constrained_config = harness.config.as_ref().clone();
@@ -828,7 +828,7 @@ mod phase2 {
let retry_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim global job after sandbox policy failure");
pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty);
@@ -840,7 +840,7 @@ mod phase2 {
#[tokio::test]
async fn dispatch_marks_job_for_retry_when_syncing_artifacts_fails() {
let harness = DispatchHarness::new().await;
harness.seed_stage1_output(100).await;
harness.seed_stage1_output(/*source_updated_at*/ 100).await;
let root = memory_root(&harness.config.codex_home);
tokio::fs::write(&root, "not a directory")
.await
@@ -850,7 +850,7 @@ mod phase2 {
let retry_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim global job after sync failure");
pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty);
@@ -862,7 +862,7 @@ mod phase2 {
#[tokio::test]
async fn dispatch_marks_job_for_retry_when_rebuilding_raw_memories_fails() {
let harness = DispatchHarness::new().await;
harness.seed_stage1_output(100).await;
harness.seed_stage1_output(/*source_updated_at*/ 100).await;
let root = memory_root(&harness.config.codex_home);
tokio::fs::create_dir_all(raw_memories_file(&root))
.await
@@ -872,7 +872,7 @@ mod phase2 {
let retry_claim = harness
.state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim global job after rebuild failure");
pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty);
@@ -917,7 +917,13 @@ mod phase2 {
.expect("upsert thread metadata");
let claim = state_db
.try_claim_stage1_job(thread_id, session.conversation_id, 100, 3_600, 64)
.try_claim_stage1_job(
thread_id,
session.conversation_id,
/*source_updated_at*/ 100,
/*lease_seconds*/ 3_600,
/*max_running_jobs*/ 64,
)
.await
.expect("claim stage-1 job");
let ownership_token = match claim {
@@ -929,10 +935,10 @@ mod phase2 {
.mark_stage1_job_succeeded(
thread_id,
&ownership_token,
100,
/*source_updated_at*/ 100,
"raw memory",
"rollout summary",
None,
/*rollout_slug*/ None,
)
.await
.expect("mark stage-1 success"),
@@ -942,7 +948,7 @@ mod phase2 {
phase2::run(&session, Arc::clone(&config)).await;
let retry_claim = state_db
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
.await
.expect("claim global job after spawn failure");
pretty_assertions::assert_eq!(
+4 -4
View File
@@ -37,8 +37,8 @@ async fn lookup_reads_history_entries() {
let (log_id, count) = history_metadata_for_file(&history_path).await;
assert_eq!(count, entries.len());
let second_entry =
lookup_history_entry(&history_path, log_id, 1).expect("fetch second history entry");
let second_entry = lookup_history_entry(&history_path, log_id, /*offset*/ 1)
.expect("fetch second history entry");
assert_eq!(second_entry, entries[1]);
}
@@ -80,8 +80,8 @@ async fn lookup_uses_stable_log_id_after_appends() {
)
.expect("append history entry");
let fetched =
lookup_history_entry(&history_path, log_id, 1).expect("lookup appended history entry");
let fetched = lookup_history_entry(&history_path, log_id, /*offset*/ 1)
.expect("lookup appended history entry");
assert_eq!(fetched, appended);
}

Some files were not shown because too many files have changed in this diff Show More