mirror of
https://github.com/openai/codex.git
synced 2026-05-15 16:53:05 +00:00
Merge 629d74dc15 into sapling-pr-archive-bolinfest
This commit is contained in:
@@ -29,6 +29,7 @@ use codex_app_server_protocol::TurnStatus;
|
||||
use codex_app_server_protocol::UserInput;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
@@ -216,6 +217,88 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_fork_preserves_persisted_workspace_roots_when_request_omits_them() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
|
||||
let repo = TempDir::new()?;
|
||||
let shared = TempDir::new()?;
|
||||
let workspace_roots = vec![
|
||||
AbsolutePathBuf::try_from(repo.path())?,
|
||||
AbsolutePathBuf::try_from(shared.path())?,
|
||||
];
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
cwd: Some(repo.path().display().to_string()),
|
||||
permissions: Some(":workspace".to_string()),
|
||||
workspace_roots: Some(workspace_roots.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
|
||||
|
||||
let turn_id = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![UserInput::Text {
|
||||
text: "seed history".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(turn_id)),
|
||||
)
|
||||
.await??;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let fork_id = mcp
|
||||
.send_thread_fork_request(ThreadForkParams {
|
||||
thread_id: thread.id,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let fork_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(fork_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadForkResponse {
|
||||
workspace_roots: forked_roots,
|
||||
permission_profile,
|
||||
..
|
||||
} = to_response::<ThreadForkResponse>(fork_resp)?;
|
||||
|
||||
assert_eq!(forked_roots, workspace_roots);
|
||||
let permission_profile: codex_protocol::models::PermissionProfile = permission_profile
|
||||
.expect("fork response should include exact permissions")
|
||||
.into();
|
||||
assert!(
|
||||
permission_profile
|
||||
.file_system_sandbox_policy()
|
||||
.can_write_path_with_cwd(shared.path(), repo.path())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_fork_can_load_source_by_path() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
|
||||
@@ -3090,6 +3090,64 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_resume_preserves_persisted_workspace_roots_when_request_omits_them() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
|
||||
let repo = TempDir::new()?;
|
||||
let shared = TempDir::new()?;
|
||||
let workspace_roots = vec![
|
||||
AbsolutePathBuf::try_from(repo.path())?,
|
||||
AbsolutePathBuf::try_from(shared.path())?,
|
||||
];
|
||||
|
||||
let RestartedThreadFixture {
|
||||
mut mcp, thread_id, ..
|
||||
} = start_materialized_thread_with_params_and_restart(
|
||||
codex_home.path(),
|
||||
"seed history",
|
||||
ThreadStartParams {
|
||||
model: Some("gpt-5.4".to_string()),
|
||||
cwd: Some(repo.path().display().to_string()),
|
||||
permissions: Some(":workspace".to_string()),
|
||||
workspace_roots: Some(workspace_roots.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let resume_id = mcp
|
||||
.send_thread_resume_request(ThreadResumeParams {
|
||||
thread_id,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let resume_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadResumeResponse {
|
||||
workspace_roots: resumed_roots,
|
||||
permission_profile,
|
||||
..
|
||||
} = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
|
||||
assert_eq!(resumed_roots, workspace_roots);
|
||||
let permission_profile: codex_protocol::models::PermissionProfile = permission_profile
|
||||
.expect("resume response should include exact permissions")
|
||||
.into();
|
||||
assert!(
|
||||
permission_profile
|
||||
.file_system_sandbox_policy()
|
||||
.can_write_path_with_cwd(shared.path(), repo.path())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct RestartedThreadFixture {
|
||||
mcp: McpProcess,
|
||||
thread_id: String,
|
||||
@@ -3100,16 +3158,27 @@ struct RestartedThreadFixture {
|
||||
async fn start_materialized_thread_and_restart(
|
||||
codex_home: &Path,
|
||||
seed_text: &str,
|
||||
) -> Result<RestartedThreadFixture> {
|
||||
start_materialized_thread_with_params_and_restart(
|
||||
codex_home,
|
||||
seed_text,
|
||||
ThreadStartParams {
|
||||
model: Some("gpt-5.4".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn start_materialized_thread_with_params_and_restart(
|
||||
codex_home: &Path,
|
||||
seed_text: &str,
|
||||
start_params: ThreadStartParams,
|
||||
) -> Result<RestartedThreadFixture> {
|
||||
let mut first_mcp = McpProcess::new(codex_home).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, first_mcp.initialize()).await??;
|
||||
|
||||
let start_id = first_mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
model: Some("gpt-5.4".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let start_id = first_mcp.send_thread_start_request(start_params).await?;
|
||||
let start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
first_mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
|
||||
|
||||
Reference in New Issue
Block a user