Download codex-rs/cloud-tasks/src/app.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cloud-tasks/src/app.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/cloud-tasks/src/app.rs
-
curl -L -o app.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cloud-tasks/src/app.rs
16.8 kB
| use std::time::Duration; | |
| use std::time::Instant; | |
| // Environment filter data models for the TUI | |
| pub struct EnvironmentRow { | |
| pub id: String, | |
| pub label: Option<String>, | |
| pub is_pinned: bool, | |
| pub repo_hints: Option<String>, // e.g., "openai/codex" | |
| } | |
| pub struct EnvModalState { | |
| pub query: String, | |
| pub selected: usize, | |
| } | |
| pub struct BestOfModalState { | |
| pub selected: usize, | |
| } | |
| pub enum ApplyResultLevel { | |
| Success, | |
| Partial, | |
| Error, | |
| } | |
| pub struct ApplyModalState { | |
| pub task_id: TaskId, | |
| pub title: String, | |
| pub result_message: Option<String>, | |
| pub result_level: Option<ApplyResultLevel>, | |
| pub skipped_paths: Vec<String>, | |
| pub conflict_paths: Vec<String>, | |
| pub diff_override: Option<String>, | |
| } | |
| use crate::scrollable_diff::ScrollableDiff; | |
| use codex_cloud_tasks_client::CloudBackend; | |
| use codex_cloud_tasks_client::TaskId; | |
| use codex_cloud_tasks_client::TaskSummary; | |
| pub struct App { | |
| pub tasks: Vec<TaskSummary>, | |
| pub selected: usize, | |
| pub status: String, | |
| pub diff_overlay: Option<DiffOverlay>, | |
| pub spinner_start: Option<Instant>, | |
| pub refresh_inflight: bool, | |
| pub details_inflight: bool, | |
| // Environment filter state | |
| pub env_filter: Option<String>, | |
| pub env_modal: Option<EnvModalState>, | |
| pub apply_modal: Option<ApplyModalState>, | |
| pub best_of_modal: Option<BestOfModalState>, | |
| pub environments: Vec<EnvironmentRow>, | |
| pub env_last_loaded: Option<std::time::Instant>, | |
| pub env_loading: bool, | |
| pub env_error: Option<String>, | |
| // New Task page | |
| pub new_task: Option<crate::new_task::NewTaskPage>, | |
| pub best_of_n: usize, | |
| // Apply preflight spinner state | |
| pub apply_preflight_inflight: bool, | |
| // Apply action spinner state | |
| pub apply_inflight: bool, | |
| // Background enrichment coordination | |
| pub list_generation: u64, | |
| pub in_flight: std::collections::HashSet<String>, | |
| // Background enrichment caches were planned; currently unused. | |
| } | |
| impl App { | |
| pub fn new() -> Self { | |
| Self { | |
| tasks: Vec::new(), | |
| selected: 0, | |
| status: "Press r to refresh".to_string(), | |
| diff_overlay: None, | |
| spinner_start: None, | |
| refresh_inflight: false, | |
| details_inflight: false, | |
| env_filter: None, | |
| env_modal: None, | |
| apply_modal: None, | |
| best_of_modal: None, | |
| environments: Vec::new(), | |
| env_last_loaded: None, | |
| env_loading: false, | |
| env_error: None, | |
| new_task: None, | |
| best_of_n: 1, | |
| apply_preflight_inflight: false, | |
| apply_inflight: false, | |
| list_generation: 0, | |
| in_flight: std::collections::HashSet::new(), | |
| } | |
| } | |
| pub fn next(&mut self) { | |
| if self.tasks.is_empty() { | |
| return; | |
| } | |
| self.selected = (self.selected + 1).min(self.tasks.len().saturating_sub(1)); | |
| } | |
| pub fn prev(&mut self) { | |
| if self.tasks.is_empty() { | |
| return; | |
| } | |
| if self.selected > 0 { | |
| self.selected -= 1; | |
| } | |
| } | |
| } | |
| pub async fn load_tasks( | |
| backend: &dyn CloudBackend, | |
| env: Option<&str>, | |
| ) -> anyhow::Result<Vec<TaskSummary>> { | |
| // In later milestones, add a small debounce, spinner, and error display. | |
| let tasks = tokio::time::timeout( | |
| Duration::from_secs(5), | |
| backend.list_tasks(env, Some(20), /*cursor*/ None), | |
| ) | |
| .await??; | |
| // Hide review-only tasks from the main list. | |
| let filtered: Vec<TaskSummary> = tasks.tasks.into_iter().filter(|t| !t.is_review).collect(); | |
| Ok(filtered) | |
| } | |
| pub struct DiffOverlay { | |
| pub title: String, | |
| pub task_id: TaskId, | |
| pub sd: ScrollableDiff, | |
| pub base_can_apply: bool, | |
| pub diff_lines: Vec<String>, | |
| pub text_lines: Vec<String>, | |
| pub prompt: Option<String>, | |
| pub attempts: Vec<AttemptView>, | |
| pub selected_attempt: usize, | |
| pub current_view: DetailView, | |
| pub base_turn_id: Option<String>, | |
| pub sibling_turn_ids: Vec<String>, | |
| pub attempt_total_hint: Option<usize>, | |
| } | |
| pub struct AttemptView { | |
| pub turn_id: Option<String>, | |
| pub status: codex_cloud_tasks_client::AttemptStatus, | |
| pub attempt_placement: Option<i64>, | |
| pub diff_lines: Vec<String>, | |
| pub text_lines: Vec<String>, | |
| pub prompt: Option<String>, | |
| pub diff_raw: Option<String>, | |
| } | |
| impl AttemptView { | |
| pub fn has_diff(&self) -> bool { | |
| !self.diff_lines.is_empty() | |
| } | |
| pub fn has_text(&self) -> bool { | |
| !self.text_lines.is_empty() || self.prompt.is_some() | |
| } | |
| } | |
| impl DiffOverlay { | |
| pub fn new(task_id: TaskId, title: String, attempt_total_hint: Option<usize>) -> Self { | |
| let mut sd = ScrollableDiff::new(); | |
| sd.set_content(Vec::new()); | |
| Self { | |
| title, | |
| task_id, | |
| sd, | |
| base_can_apply: false, | |
| diff_lines: Vec::new(), | |
| text_lines: Vec::new(), | |
| prompt: None, | |
| attempts: vec![AttemptView::default()], | |
| selected_attempt: 0, | |
| current_view: DetailView::Prompt, | |
| base_turn_id: None, | |
| sibling_turn_ids: Vec::new(), | |
| attempt_total_hint, | |
| } | |
| } | |
| pub fn current_attempt(&self) -> Option<&AttemptView> { | |
| self.attempts.get(self.selected_attempt) | |
| } | |
| pub fn base_attempt_mut(&mut self) -> &mut AttemptView { | |
| if self.attempts.is_empty() { | |
| self.attempts.push(AttemptView::default()); | |
| } | |
| &mut self.attempts[0] | |
| } | |
| pub fn set_view(&mut self, view: DetailView) { | |
| self.current_view = view; | |
| self.apply_selection_to_fields(); | |
| } | |
| pub fn expected_attempts(&self) -> Option<usize> { | |
| self.attempt_total_hint.or({ | |
| if self.attempts.is_empty() { | |
| None | |
| } else { | |
| Some(self.attempts.len()) | |
| } | |
| }) | |
| } | |
| pub fn attempt_count(&self) -> usize { | |
| self.attempts.len() | |
| } | |
| pub fn attempt_display_total(&self) -> usize { | |
| self.expected_attempts() | |
| .unwrap_or_else(|| self.attempts.len().max(1)) | |
| } | |
| pub fn step_attempt(&mut self, delta: isize) -> bool { | |
| let total = self.attempts.len(); | |
| if total <= 1 { | |
| return false; | |
| } | |
| let total_isize = total as isize; | |
| let current = self.selected_attempt as isize; | |
| let mut next = current + delta; | |
| next = ((next % total_isize) + total_isize) % total_isize; | |
| let next = next as usize; | |
| self.selected_attempt = next; | |
| self.apply_selection_to_fields(); | |
| true | |
| } | |
| pub fn current_can_apply(&self) -> bool { | |
| matches!(self.current_view, DetailView::Diff) | |
| && self | |
| .current_attempt() | |
| .and_then(|attempt| attempt.diff_raw.as_ref()) | |
| .map(|diff| !diff.is_empty()) | |
| .unwrap_or(false) | |
| } | |
| pub fn apply_selection_to_fields(&mut self) { | |
| let (diff_lines, text_lines, prompt) = if let Some(attempt) = self.current_attempt() { | |
| ( | |
| attempt.diff_lines.clone(), | |
| attempt.text_lines.clone(), | |
| attempt.prompt.clone(), | |
| ) | |
| } else { | |
| self.diff_lines.clear(); | |
| self.text_lines.clear(); | |
| self.prompt = None; | |
| self.sd.set_content(vec!["<loading attempt>".to_string()]); | |
| return; | |
| }; | |
| self.diff_lines = diff_lines.clone(); | |
| self.text_lines = text_lines.clone(); | |
| self.prompt = prompt; | |
| match self.current_view { | |
| DetailView::Diff => { | |
| if diff_lines.is_empty() { | |
| self.sd.set_content(vec!["<no diff available>".to_string()]); | |
| } else { | |
| self.sd.set_content(diff_lines); | |
| } | |
| } | |
| DetailView::Prompt => { | |
| if text_lines.is_empty() { | |
| self.sd.set_content(vec!["<no output>".to_string()]); | |
| } else { | |
| self.sd.set_content(text_lines); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| pub enum DetailView { | |
| Diff, | |
| Prompt, | |
| } | |
| /// Internal app events delivered from background tasks. | |
| /// These let the UI event loop remain responsive and keep the spinner animating. | |
| pub enum AppEvent { | |
| TasksLoaded { | |
| env: Option<String>, | |
| result: anyhow::Result<Vec<TaskSummary>>, | |
| }, | |
| // Background diff summary events were planned; removed for now to keep code minimal. | |
| /// Autodetection of a likely environment id finished | |
| EnvironmentAutodetected(anyhow::Result<crate::env_detect::AutodetectSelection>), | |
| /// Background completion of environment list fetch | |
| EnvironmentsLoaded(anyhow::Result<Vec<EnvironmentRow>>), | |
| DetailsDiffLoaded { | |
| id: TaskId, | |
| title: String, | |
| diff: String, | |
| }, | |
| DetailsMessagesLoaded { | |
| id: TaskId, | |
| title: String, | |
| messages: Vec<String>, | |
| prompt: Option<String>, | |
| turn_id: Option<String>, | |
| sibling_turn_ids: Vec<String>, | |
| attempt_placement: Option<i64>, | |
| attempt_status: codex_cloud_tasks_client::AttemptStatus, | |
| }, | |
| DetailsFailed { | |
| id: TaskId, | |
| title: String, | |
| error: String, | |
| }, | |
| AttemptsLoaded { | |
| id: TaskId, | |
| attempts: Vec<codex_cloud_tasks_client::TurnAttempt>, | |
| }, | |
| /// Background completion of new task submission | |
| NewTaskSubmitted(Result<codex_cloud_tasks_client::CreatedTask, String>), | |
| /// Background completion of apply preflight when opening modal or on demand | |
| ApplyPreflightFinished { | |
| id: TaskId, | |
| title: String, | |
| message: String, | |
| level: ApplyResultLevel, | |
| skipped: Vec<String>, | |
| conflicts: Vec<String>, | |
| }, | |
| /// Background completion of apply action (actual patch application) | |
| ApplyFinished { | |
| id: TaskId, | |
| result: std::result::Result<codex_cloud_tasks_client::ApplyOutcome, String>, | |
| }, | |
| } | |
| // Convenience aliases; currently unused. | |
| mod tests { | |
| use super::*; | |
| use chrono::Utc; | |
| use codex_cloud_tasks_client::CloudBackendFuture; | |
| use codex_cloud_tasks_client::CloudTaskError; | |
| struct FakeBackend { | |
| // maps env key to titles | |
| by_env: std::collections::HashMap<Option<String>, Vec<&'static str>>, | |
| } | |
| impl FakeBackend { | |
| async fn list_tasks( | |
| &self, | |
| env: Option<&str>, | |
| limit: Option<i64>, | |
| cursor: Option<&str>, | |
| ) -> Result<codex_cloud_tasks_client::TaskListPage, CloudTaskError> { | |
| let key = env.map(str::to_string); | |
| let titles = self | |
| .by_env | |
| .get(&key) | |
| .cloned() | |
| .unwrap_or_else(|| vec!["default-a", "default-b"]); | |
| let mut out = Vec::new(); | |
| for (i, t) in titles.into_iter().enumerate() { | |
| out.push(TaskSummary { | |
| id: TaskId(format!("T-{i}")), | |
| title: t.to_string(), | |
| status: codex_cloud_tasks_client::TaskStatus::Ready, | |
| updated_at: Utc::now(), | |
| environment_id: env.map(str::to_string), | |
| environment_label: None, | |
| summary: codex_cloud_tasks_client::DiffSummary::default(), | |
| is_review: false, | |
| attempt_total: Some(1), | |
| }); | |
| } | |
| let max = limit.unwrap_or(i64::MAX); | |
| let max = max.min(20); | |
| let mut limited = Vec::new(); | |
| for task in out { | |
| if (limited.len() as i64) >= max { | |
| break; | |
| } | |
| limited.push(task); | |
| } | |
| Ok(codex_cloud_tasks_client::TaskListPage { | |
| tasks: limited, | |
| cursor: cursor.map(str::to_string), | |
| }) | |
| } | |
| async fn get_task_summary(&self, id: TaskId) -> Result<TaskSummary, CloudTaskError> { | |
| self.list_tasks(/*env*/ None, /*limit*/ None, /*cursor*/ None) | |
| .await? | |
| .tasks | |
| .into_iter() | |
| .find(|t| t.id == id) | |
| .ok_or_else(|| CloudTaskError::Msg(format!("Task {} not found", id.0))) | |
| } | |
| async fn get_task_text( | |
| &self, | |
| _id: TaskId, | |
| ) -> Result<codex_cloud_tasks_client::TaskText, CloudTaskError> { | |
| Ok(codex_cloud_tasks_client::TaskText { | |
| prompt: Some("Example prompt".to_string()), | |
| messages: Vec::new(), | |
| turn_id: Some("fake-turn".to_string()), | |
| sibling_turn_ids: Vec::new(), | |
| attempt_placement: Some(0), | |
| attempt_status: codex_cloud_tasks_client::AttemptStatus::Completed, | |
| }) | |
| } | |
| } | |
| impl codex_cloud_tasks_client::CloudBackend for FakeBackend { | |
| fn list_tasks<'a>( | |
| &'a self, | |
| env: Option<&'a str>, | |
| limit: Option<i64>, | |
| cursor: Option<&'a str>, | |
| ) -> CloudBackendFuture<'a, codex_cloud_tasks_client::TaskListPage> { | |
| Box::pin(FakeBackend::list_tasks(self, env, limit, cursor)) | |
| } | |
| fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary> { | |
| Box::pin(FakeBackend::get_task_summary(self, id)) | |
| } | |
| fn get_task_diff(&self, _id: TaskId) -> CloudBackendFuture<'_, Option<String>> { | |
| Box::pin(async { | |
| Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented( | |
| "not used in test", | |
| )) | |
| }) | |
| } | |
| fn get_task_messages(&self, _id: TaskId) -> CloudBackendFuture<'_, Vec<String>> { | |
| Box::pin(async { Ok(vec![]) }) | |
| } | |
| fn get_task_text( | |
| &self, | |
| id: TaskId, | |
| ) -> CloudBackendFuture<'_, codex_cloud_tasks_client::TaskText> { | |
| Box::pin(FakeBackend::get_task_text(self, id)) | |
| } | |
| fn list_sibling_attempts( | |
| &self, | |
| _task: TaskId, | |
| _turn_id: String, | |
| ) -> CloudBackendFuture<'_, Vec<codex_cloud_tasks_client::TurnAttempt>> { | |
| Box::pin(async { Ok(Vec::new()) }) | |
| } | |
| fn apply_task( | |
| &self, | |
| _id: TaskId, | |
| _diff_override: Option<String>, | |
| ) -> CloudBackendFuture<'_, codex_cloud_tasks_client::ApplyOutcome> { | |
| Box::pin(async { | |
| Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented( | |
| "not used in test", | |
| )) | |
| }) | |
| } | |
| fn apply_task_preflight( | |
| &self, | |
| _id: TaskId, | |
| _diff_override: Option<String>, | |
| ) -> CloudBackendFuture<'_, codex_cloud_tasks_client::ApplyOutcome> { | |
| Box::pin(async { | |
| Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented( | |
| "not used in test", | |
| )) | |
| }) | |
| } | |
| fn create_task<'a>( | |
| &'a self, | |
| _env_id: &'a str, | |
| _prompt: &'a str, | |
| _git_ref: &'a str, | |
| _qa_mode: bool, | |
| _best_of_n: usize, | |
| ) -> CloudBackendFuture<'a, codex_cloud_tasks_client::CreatedTask> { | |
| Box::pin(async { | |
| Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented( | |
| "not used in test", | |
| )) | |
| }) | |
| } | |
| } | |
| async fn load_tasks_uses_env_parameter() { | |
| // Arrange: env-specific task titles | |
| let mut by_env = std::collections::HashMap::new(); | |
| by_env.insert(None, vec!["root-1", "root-2"]); | |
| by_env.insert(Some("env-A".to_string()), vec!["A-1"]); | |
| by_env.insert(Some("env-B".to_string()), vec!["B-1", "B-2", "B-3"]); | |
| let backend = FakeBackend { by_env }; | |
| // Act + Assert | |
| let root = load_tasks(&backend, /*env*/ None).await.unwrap(); | |
| assert_eq!(root.len(), 2); | |
| assert_eq!(root[0].title, "root-1"); | |
| let a = load_tasks(&backend, Some("env-A")).await.unwrap(); | |
| assert_eq!(a.len(), 1); | |
| assert_eq!(a[0].title, "A-1"); | |
| let b = load_tasks(&backend, Some("env-B")).await.unwrap(); | |
| assert_eq!(b.len(), 3); | |
| assert_eq!(b[2].title, "B-3"); | |
| } | |
| } | |