mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
chore: clean up argument-comment lint and roll out all-target CI on macOS (#16054)
## Why `argument-comment-lint` was green in CI even though the repo still had many uncommented literal arguments. The main gap was target coverage: the repo wrapper did not force Cargo to inspect test-only call sites, so examples like the `latest_session_lookup_params(true, ...)` tests in `codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path. This change cleans up the existing backlog, makes the default repo lint path cover all Cargo targets, and starts rolling that stricter CI enforcement out on the platform where it is currently validated. ## What changed - mechanically fixed existing `argument-comment-lint` violations across the `codex-rs` workspace, including tests, examples, and benches - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and `tools/argument-comment-lint/run.sh` so non-`--fix` runs default to `--all-targets` unless the caller explicitly narrows the target set - fixed both wrappers so forwarded cargo arguments after `--` are preserved with a single separator - documented the new default behavior in `tools/argument-comment-lint/README.md` - updated `rust-ci` so the macOS lint lane keeps the plain wrapper invocation and therefore enforces `--all-targets`, while Linux and Windows temporarily pass `-- --lib --bins` That temporary CI split keeps the stricter all-targets check where it is already cleaned up, while leaving room to finish the remaining Linux- and Windows-specific target-gated cleanup before enabling `--all-targets` on those runners. The Linux and Windows failures on the intermediate revision were caused by the wrapper forwarding bug, not by additional lint findings in those lanes. ## Validation - `bash -n tools/argument-comment-lint/run.sh` - `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh` - shell-level wrapper forwarding check for `-- --lib --bins` - shell-level wrapper forwarding check for `-- --tests` - `just argument-comment-lint` - `cargo test` in `tools/argument-comment-lint` - `cargo test -p codex-terminal-detection` ## Follow-up - Clean up remaining Linux-only target-gated callsites, then switch the Linux lint lane back to the plain wrapper invocation. - Clean up remaining Windows-only target-gated callsites, then switch the Windows lint lane back to the plain wrapper invocation.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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![
|
||||
|
||||
@@ -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: "));
|
||||
}
|
||||
|
||||
@@ -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`)"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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![(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()),
|
||||
);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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, &[])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 },
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!({})),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ fn default_mode_instructions_replace_mode_names_placeholder() {
|
||||
let expected_snippet = format!("Known mode names are {known_mode_names}.");
|
||||
assert!(default_instructions.contains(&expected_snippet));
|
||||
|
||||
let expected_availability_message =
|
||||
request_user_input_availability_message(ModeKind::Default, true);
|
||||
let expected_availability_message = request_user_input_availability_message(
|
||||
ModeKind::Default,
|
||||
/*default_mode_request_user_input*/ true,
|
||||
);
|
||||
assert!(default_instructions.contains(&expected_availability_message));
|
||||
assert!(default_instructions.contains("prefer using the `request_user_input` tool"));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user