use chrono::DateTime; use chrono::Utc; use serde::Deserialize; use serde::Serialize; pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum CloudTaskError { #[error("unimplemented: {0}")] Unimplemented(&'static str), #[error("http error: {0}")] Http(String), #[error("io error: {0}")] Io(String), #[error("{0}")] Msg(String), } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct TaskId(pub String); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum TaskStatus { Pending, Ready, Applied, Error, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct TaskSummary { pub id: TaskId, pub title: String, pub status: TaskStatus, pub updated_at: DateTime, /// Backend environment identifier (when available) pub environment_id: Option, /// Human-friendly environment label (when available) pub environment_label: Option, pub summary: DiffSummary, /// True when the backend reports this task as a code review. #[serde(default)] pub is_review: bool, /// Number of assistant attempts (best-of-N), when reported by the backend. #[serde(default)] pub attempt_total: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum AttemptStatus { Pending, InProgress, Completed, Failed, Cancelled, #[default] Unknown, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TurnAttempt { pub turn_id: String, pub attempt_placement: Option, pub created_at: Option>, pub status: AttemptStatus, pub diff: Option, pub messages: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ApplyStatus { Success, Partial, Error, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ApplyOutcome { pub applied: bool, pub status: ApplyStatus, pub message: String, #[serde(default)] pub skipped_paths: Vec, #[serde(default)] pub conflict_paths: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CreatedTask { pub id: TaskId, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct DiffSummary { pub files_changed: usize, pub lines_added: usize, pub lines_removed: usize, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct TaskText { pub prompt: Option, pub messages: Vec, pub turn_id: Option, pub sibling_turn_ids: Vec, pub attempt_placement: Option, pub attempt_status: AttemptStatus, } impl Default for TaskText { fn default() -> Self { Self { prompt: None, messages: Vec::new(), turn_id: None, sibling_turn_ids: Vec::new(), attempt_placement: None, attempt_status: AttemptStatus::Unknown, } } } #[async_trait::async_trait] pub trait CloudBackend: Send + Sync { async fn list_tasks(&self, env: Option<&str>) -> Result>; async fn get_task_diff(&self, id: TaskId) -> Result>; /// Return assistant output messages (no diff) when available. async fn get_task_messages(&self, id: TaskId) -> Result>; /// Return the creating prompt and assistant messages (when available). async fn get_task_text(&self, id: TaskId) -> Result; /// Return any sibling attempts (best-of-N) for the given assistant turn. async fn list_sibling_attempts( &self, task: TaskId, turn_id: String, ) -> Result>; /// Dry-run apply (preflight) that validates whether the patch would apply cleanly. /// Never modifies the working tree. When `diff_override` is supplied, the provided diff is /// used instead of re-fetching the task details so callers can apply alternate attempts. async fn apply_task_preflight( &self, id: TaskId, diff_override: Option, ) -> Result; async fn apply_task(&self, id: TaskId, diff_override: Option) -> Result; async fn create_task( &self, env_id: &str, prompt: &str, git_ref: &str, qa_mode: bool, best_of_n: usize, ) -> Result; }