From 4dc32e8c5eb90ee1a5617bdd10fd922283bc7af7 Mon Sep 17 00:00:00 2001 From: Friel Date: Sun, 29 Mar 2026 21:28:15 +0000 Subject: [PATCH 1/2] feat(core): reset watchdog on owner input --- codex-rs/core/src/agent/control.rs | 11 +++++++ codex-rs/core/src/agent/control_tests.rs | 42 ++++++++++++++++++++++++ codex-rs/core/src/agent/watchdog.rs | 21 ++++++++++++ codex-rs/core/src/config/mod.rs | 2 +- 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index c79eeee095..b98bf4fada 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -670,6 +670,7 @@ impl AgentControl { ) .await; if result.is_ok() { + self.watchdogs.note_owner_input(agent_id).await; self.state .update_last_task_message(agent_id, last_task_message); } @@ -1255,6 +1256,16 @@ impl AgentControl { self.watchdogs.force_due_for_tests(target_thread_id).await; } + #[cfg(test)] + pub(crate) async fn watchdog_owner_idle_since_is_none_for_tests( + &self, + target_thread_id: ThreadId, + ) -> Option { + self.watchdogs + .owner_idle_since_is_none_for_tests(target_thread_id) + .await + } + #[cfg(test)] pub(crate) async fn set_watchdog_active_helper_for_tests( &self, diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index e7e700d71b..a83b5e63ec 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -417,6 +417,48 @@ async fn send_input_submits_user_message() { assert_eq!(captured, Some(expected)); } +#[tokio::test] +async fn send_input_resets_watchdog_owner_idle_state() { + let harness = AgentControlHarness::new().await; + let (owner_thread_id, _thread) = harness.start_thread().await; + let target_thread_id = ThreadId::new(); + + harness + .control + .register_watchdog(WatchdogRegistration { + owner_thread_id, + target_thread_id, + child_depth: 0, + interval_s: 30, + prompt: String::new(), + config: harness.config.clone(), + }) + .await + .expect("watchdog registration should succeed"); + + assert_eq!( + harness + .control + .watchdog_owner_idle_since_is_none_for_tests(target_thread_id) + .await, + Some(false) + ); + + harness + .control + .send_input(owner_thread_id, text_input("ping")) + .await + .expect("send_input should succeed"); + + assert_eq!( + harness + .control + .watchdog_owner_idle_since_is_none_for_tests(target_thread_id) + .await, + Some(true) + ); +} + #[test] fn build_agent_inbox_items_emits_function_call_and_output() { let sender_thread_id = ThreadId::new(); diff --git a/codex-rs/core/src/agent/watchdog.rs b/codex-rs/core/src/agent/watchdog.rs index 86b068ed7f..71d4246a83 100644 --- a/codex-rs/core/src/agent/watchdog.rs +++ b/codex-rs/core/src/agent/watchdog.rs @@ -448,6 +448,16 @@ impl WatchdogManager { true } + pub(crate) async fn note_owner_input(&self, owner_thread_id: ThreadId) { + let mut registrations = self.registrations.lock().await; + for entry in registrations.values_mut() { + if entry.registration.owner_thread_id == owner_thread_id { + entry.owner_idle_since = None; + entry.owner_was_running = true; + } + } + } + #[cfg(test)] pub(crate) async fn force_due_for_tests(&self, target_thread_id: ThreadId) { let mut registrations = self.registrations.lock().await; @@ -456,6 +466,17 @@ impl WatchdogManager { } } + #[cfg(test)] + pub(crate) async fn owner_idle_since_is_none_for_tests( + &self, + target_thread_id: ThreadId, + ) -> Option { + let registrations = self.registrations.lock().await; + registrations + .get(&target_thread_id) + .map(|entry| entry.owner_idle_since.is_none()) + } + async fn update_after_spawn( &self, target_thread_id: ThreadId, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 8cd59fa659..a187a4971a 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -145,7 +145,7 @@ pub(crate) const PROJECT_DOC_MAX_BYTES: usize = 32 * 1024; // 32 KiB pub(crate) const DEFAULT_AGENT_MAX_THREADS: Option = Some(6); pub(crate) const DEFAULT_AGENT_MAX_DEPTH: i32 = 1; pub(crate) const DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS: Option = None; -pub(crate) const DEFAULT_WATCHDOG_INTERVAL_S: i64 = 60; +pub(crate) const DEFAULT_WATCHDOG_INTERVAL_S: i64 = 10; pub const CONFIG_TOML_FILE: &str = "config.toml"; const OPENAI_BASE_URL_ENV_VAR: &str = "OPENAI_BASE_URL"; From 7430b774949dd104cc583c172a28cc0935fbdac6 Mon Sep 17 00:00:00 2001 From: Friel Date: Mon, 30 Mar 2026 08:44:52 +0000 Subject: [PATCH 2/2] ci: bump sdk and arglint timeouts --- .github/workflows/rust-ci.yml | 4 ++-- .github/workflows/sdk.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index a655b3071e..444b79ee74 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -137,10 +137,10 @@ jobs: include: - name: Linux runner: ubuntu-24.04 - timeout_minutes: 30 + timeout_minutes: 45 - name: macOS runner: macos-15-xlarge - timeout_minutes: 30 + timeout_minutes: 45 - name: Windows runner: windows-x64 timeout_minutes: 30 diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 45c983ac1e..87adc96283 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -10,7 +10,7 @@ jobs: runs-on: group: codex-runners labels: codex-linux-x64 - timeout-minutes: 10 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6