feat(core): add watchdog runtime and prompts

Preserve the watchdog runtime and prompt behavior on top of the refreshed inbox branch and collapse the branch back to a single commit for easier future restacks.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Friel
2026-03-14 13:32:00 -07:00
parent afaa5e887c
commit 3e8bd6b801
42 changed files with 2635 additions and 113 deletions

View File

@@ -407,6 +407,62 @@ async fn thread_read_include_turns_rejects_unmaterialized_loaded_thread() -> Res
Ok(())
}
#[tokio::test]
async fn thread_read_loaded_ephemeral_thread_ignores_unrelated_rollout_mentions() -> 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 mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
model: Some("mock-model".to_string()),
ephemeral: Some(true),
..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 unrelated_preview = thread.id.clone();
let _unrelated_rollout_id = create_fake_rollout_with_text_elements(
codex_home.path(),
"2025-01-05T13-00-00",
"2025-01-05T13:00:00Z",
&unrelated_preview,
vec![],
Some("mock_provider"),
None,
)?;
let read_id = mcp
.send_thread_read_request(ThreadReadParams {
thread_id: thread.id.clone(),
include_turns: false,
})
.await?;
let read_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(read_id)),
)
.await??;
let ThreadReadResponse { thread: read } = to_response::<ThreadReadResponse>(read_resp)?;
assert_eq!(read.id, thread.id);
assert!(read.ephemeral);
assert_eq!(read.path, None);
assert!(read.preview.is_empty());
assert_eq!(read.status, ThreadStatus::Idle);
Ok(())
}
#[tokio::test]
async fn thread_read_reports_system_error_idle_flag_after_failed_turn() -> Result<()> {
let server = responses::start_mock_server().await;

View File

@@ -215,9 +215,12 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> {
]),
&zsh_path,
)?;
// This flow can require several sequential approval round-trips on slower
// macOS runners before the parent command reaches a terminal state.
let read_timeout = std::time::Duration::from_secs(20);
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
timeout(read_timeout, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
@@ -227,7 +230,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> {
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
read_timeout,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
@@ -348,9 +351,12 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> {
]),
&zsh_path,
)?;
// This flow can require several sequential approval round-trips on slower
// macOS runners before the parent command reaches a terminal state.
let read_timeout = std::time::Duration::from_secs(20);
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
timeout(read_timeout, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
@@ -360,7 +366,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> {
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
read_timeout,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
@@ -507,9 +513,10 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
]),
&zsh_path,
)?;
let read_timeout = std::time::Duration::from_secs(20);
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
timeout(read_timeout, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
@@ -519,7 +526,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
read_timeout,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
@@ -548,7 +555,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
})
.await?;
let turn_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
read_timeout,
mcp.read_stream_until_response_message(RequestId::Integer(turn_id)),
)
.await??;
@@ -566,11 +573,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
let second_file_str = second_file.to_string_lossy().into_owned();
let parent_shell_hint = format!("&& {}", &first_file_str);
while target_decision_index < target_decisions.len() || !saw_parent_approval {
let server_req = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_request_message(),
)
.await??;
let server_req = timeout(read_timeout, mcp.read_stream_until_request_message()).await??;
let ServerRequest::CommandExecutionRequestApproval { request_id, params } = server_req
else {
panic!("expected CommandExecutionRequestApproval request");
@@ -640,7 +643,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
assert_eq!(approved_subcommand_strings.len(), 2);
assert!(approved_subcommand_strings[0].contains(&first_file.display().to_string()));
assert!(approved_subcommand_strings[1].contains(&second_file.display().to_string()));
let parent_completed_command_execution = timeout(DEFAULT_READ_TIMEOUT, async {
let parent_completed_command_execution = timeout(read_timeout, async {
loop {
let completed_notif = mcp
.read_stream_until_notification_message("item/completed")
@@ -682,7 +685,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
}
match timeout(
DEFAULT_READ_TIMEOUT,
read_timeout,
mcp.read_stream_until_notification_message("turn/completed"),
)
.await
@@ -705,7 +708,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
mcp.interrupt_turn_and_wait_for_aborted(
thread.id.clone(),
turn.id.clone(),
DEFAULT_READ_TIMEOUT,
read_timeout,
)
.await?;
}
@@ -718,7 +721,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
// sandbox failures can also complete the turn before the parent
// completion item is observed.
let completed_notif = timeout(
DEFAULT_READ_TIMEOUT,
read_timeout,
mcp.read_stream_until_notification_message("turn/completed"),
)
.await??;