mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
## Summary `ChatWidget` has been carrying several independent domains in one large state bag: transcript bookkeeping, turn lifecycle, queued input, status surfaces, connectors, review mode, and protocol dispatch. That makes otherwise-local changes hard to reason about because unrelated fields and side effects live beside each other in `chatwidget.rs`. This is the first cleanup PR in a larger decomposition effort. It does not try to make `chatwidget.rs` small in one sweep; instead, it establishes focused state boundaries that later handler, popup, rendering, and effect-synchronization extractions can build on. This PR keeps `ChatWidget` as the composition layer while moving focused state into smaller `codex-tui` modules. The widget still owns effects that touch the bottom pane, app events, command submission, redraw scheduling, and terminal-title updates. ## Changes - Add focused state modules under `codex-rs/tui/src/chatwidget/` for input queues, turn lifecycle, transcript bookkeeping, status state, connectors, review mode, and app-server protocol dispatch. - Update `ChatWidget` to hold grouped state structs and route input/lifecycle/status operations through those focused helpers. - Move app-server notification dispatch into `chatwidget/protocol.rs` while leaving feature handlers and side effects on `ChatWidget`. - Replace the large manual `ChatWidget` test literal with the normal constructor plus narrow test overrides, so future state moves do not require every field to be restated in test setup. - Update existing tests to access the new grouped state or narrower helpers without changing snapshot behavior. ## Longer-term direction Follow-up PRs can continue shrinking `chatwidget.rs` by moving behavior, not just state, into focused modules: - Extract input/submission flow, turn/stream handling, and tool-cell lifecycles into domain modules that call the new state reducers. - Move popup/settings builders and rendering helpers out of the main widget file so `ChatWidget` stays focused on composition. - Reduce direct `BottomPane` mutation by applying domain-specific sync outputs at clearer boundaries.
98 lines
3.1 KiB
Rust
98 lines
3.1 KiB
Rust
//! Agent-turn lifecycle state for `ChatWidget`.
|
|
|
|
use std::collections::HashSet;
|
|
use std::time::Instant;
|
|
|
|
use codex_utils_sleep_inhibitor::SleepInhibitor;
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct TurnLifecycleState {
|
|
pub(super) sleep_inhibitor: SleepInhibitor,
|
|
/// Tracks whether codex-core currently considers an agent turn to be in progress.
|
|
pub(super) agent_turn_running: bool,
|
|
pub(super) last_turn_id: Option<String>,
|
|
pub(super) budget_limited_turn_ids: HashSet<String>,
|
|
pub(super) goal_status_active_turn_started_at: Option<Instant>,
|
|
}
|
|
|
|
impl TurnLifecycleState {
|
|
pub(super) fn new(prevent_idle_sleep: bool) -> Self {
|
|
Self {
|
|
sleep_inhibitor: SleepInhibitor::new(prevent_idle_sleep),
|
|
agent_turn_running: false,
|
|
last_turn_id: None,
|
|
budget_limited_turn_ids: HashSet::new(),
|
|
goal_status_active_turn_started_at: None,
|
|
}
|
|
}
|
|
|
|
pub(super) fn start(&mut self, now: Instant) {
|
|
self.agent_turn_running = true;
|
|
self.goal_status_active_turn_started_at = Some(now);
|
|
self.sleep_inhibitor.set_turn_running(/*turn_running*/ true);
|
|
}
|
|
|
|
pub(super) fn finish(&mut self) {
|
|
self.agent_turn_running = false;
|
|
self.goal_status_active_turn_started_at = None;
|
|
self.sleep_inhibitor
|
|
.set_turn_running(/*turn_running*/ false);
|
|
}
|
|
|
|
pub(super) fn restore_running(&mut self, running: bool, now: Instant) {
|
|
self.agent_turn_running = running;
|
|
self.goal_status_active_turn_started_at = running.then_some(now);
|
|
self.sleep_inhibitor.set_turn_running(running);
|
|
}
|
|
|
|
pub(super) fn reset_thread(&mut self) {
|
|
self.finish();
|
|
self.last_turn_id = None;
|
|
self.budget_limited_turn_ids.clear();
|
|
}
|
|
|
|
pub(super) fn set_prevent_idle_sleep(&mut self, enabled: bool) {
|
|
self.sleep_inhibitor = SleepInhibitor::new(enabled);
|
|
self.sleep_inhibitor
|
|
.set_turn_running(self.agent_turn_running);
|
|
}
|
|
|
|
pub(super) fn mark_budget_limited(&mut self, turn_id: String) {
|
|
self.budget_limited_turn_ids.insert(turn_id);
|
|
}
|
|
|
|
pub(super) fn take_budget_limited(&mut self, turn_id: &str) -> bool {
|
|
self.budget_limited_turn_ids.remove(turn_id)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn start_and_finish_update_running_state() {
|
|
let mut state = TurnLifecycleState::new(/*prevent_idle_sleep*/ false);
|
|
|
|
state.start(Instant::now());
|
|
assert!(state.agent_turn_running);
|
|
assert!(state.goal_status_active_turn_started_at.is_some());
|
|
assert!(state.sleep_inhibitor.is_turn_running());
|
|
|
|
state.finish();
|
|
assert!(!state.agent_turn_running);
|
|
assert!(state.goal_status_active_turn_started_at.is_none());
|
|
assert!(!state.sleep_inhibitor.is_turn_running());
|
|
}
|
|
|
|
#[test]
|
|
fn budget_limited_turn_ids_are_consumed() {
|
|
let mut state = TurnLifecycleState::new(/*prevent_idle_sleep*/ false);
|
|
|
|
state.mark_budget_limited("turn-1".to_string());
|
|
|
|
assert!(state.take_budget_limited("turn-1"));
|
|
assert!(!state.take_budget_limited("turn-1"));
|
|
}
|
|
}
|