//! Supervisor API methods. use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::client::{ApiClient, ApiError}; // Request/Response types #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct SpawnTaskRequest { pub name: String, pub plan: String, pub contract_id: Uuid, #[serde(skip_serializing_if = "Option::is_none")] pub parent_task_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub checkpoint_sha: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct WaitRequest { pub timeout_seconds: i32, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ReadFileRequest { pub file_path: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CreateBranchRequest { pub branch_name: String, #[serde(skip_serializing_if = "Option::is_none")] pub from_ref: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct MergeRequest { pub squash: bool, #[serde(skip_serializing_if = "Option::is_none")] pub target_branch: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CreatePrRequest { pub task_id: Uuid, pub title: String, pub body: String, pub base_branch: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CheckpointRequest { pub message: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct AskQuestionRequest { pub question: String, #[serde(skip_serializing_if = "Vec::is_empty")] pub choices: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, pub timeout_seconds: i32, /// When true, the request will block indefinitely until user responds (no timeout) pub phaseguard: bool, /// When true, allow selecting multiple choices (response will be comma-separated) #[serde(default)] pub multi_select: bool, /// When true, return immediately without waiting for response #[serde(default)] pub non_blocking: bool, /// Question type: general, phase_confirmation, or contract_complete #[serde(default)] pub question_type: String, } // Generic response type for JSON output #[derive(Deserialize, Serialize)] pub struct JsonValue(pub serde_json::Value); impl ApiClient { /// Get all tasks in a contract. pub async fn supervisor_tasks(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/supervisor/contracts/{}/tasks", contract_id)) .await } /// Get task tree structure. pub async fn supervisor_tree(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/supervisor/contracts/{}/tree", contract_id)) .await } /// Spawn a new task. pub async fn supervisor_spawn(&self, req: SpawnTaskRequest) -> Result { self.post("/api/v1/mesh/supervisor/tasks", &req).await } /// Wait for a task to complete. pub async fn supervisor_wait( &self, task_id: Uuid, timeout_seconds: i32, ) -> Result { let req = WaitRequest { timeout_seconds }; self.post(&format!("/api/v1/mesh/supervisor/tasks/{}/wait", task_id), &req) .await } /// Read a file from a task's worktree. pub async fn supervisor_read_file( &self, task_id: Uuid, file_path: &str, ) -> Result { let req = ReadFileRequest { file_path: file_path.to_string(), }; self.post(&format!("/api/v1/mesh/supervisor/tasks/{}/read-file", task_id), &req) .await } /// Create a new branch. pub async fn supervisor_branch( &self, branch_name: &str, from_ref: Option, ) -> Result { let req = CreateBranchRequest { branch_name: branch_name.to_string(), from_ref, }; self.post("/api/v1/mesh/supervisor/branches", &req).await } /// Merge a task's changes. pub async fn supervisor_merge( &self, task_id: Uuid, target_branch: Option, squash: bool, ) -> Result { let req = MergeRequest { squash, target_branch, }; self.post(&format!("/api/v1/mesh/supervisor/tasks/{}/merge", task_id), &req) .await } /// Create a pull request. pub async fn supervisor_pr( &self, task_id: Uuid, title: &str, body: &str, base_branch: &str, ) -> Result { let req = CreatePrRequest { task_id, title: title.to_string(), body: body.to_string(), base_branch: base_branch.to_string(), }; self.post("/api/v1/mesh/supervisor/pr", &req).await } /// Get task diff. pub async fn supervisor_diff(&self, task_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/supervisor/tasks/{}/diff", task_id)) .await } /// Create a checkpoint. pub async fn supervisor_checkpoint( &self, task_id: Uuid, message: &str, ) -> Result { let req = CheckpointRequest { message: message.to_string(), }; self.post(&format!("/api/v1/mesh/tasks/{}/checkpoint", task_id), &req) .await } /// List checkpoints. pub async fn supervisor_checkpoints(&self, task_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/tasks/{}/checkpoints", task_id)) .await } /// Get contract status. pub async fn supervisor_status(&self, contract_id: Uuid) -> Result { self.get(&format!("/api/v1/contracts/{}/daemon/status", contract_id)) .await } /// Ask a question and wait for user feedback. pub async fn supervisor_ask( &self, question: &str, choices: Vec, context: Option, timeout_seconds: i32, phaseguard: bool, multi_select: bool, non_blocking: bool, question_type: String, ) -> Result { let req = AskQuestionRequest { question: question.to_string(), choices, context, timeout_seconds, phaseguard, multi_select, non_blocking, question_type, }; self.post("/api/v1/mesh/supervisor/questions", &req).await } /// Advance contract to a new phase. pub async fn supervisor_advance_phase( &self, contract_id: Uuid, phase: &str, ) -> Result { #[derive(Serialize)] struct AdvancePhaseRequest { phase: String, } let req = AdvancePhaseRequest { phase: phase.to_string(), }; self.post(&format!("/api/v1/contracts/{}/phase", contract_id), &req) .await } /// Get individual task details. pub async fn supervisor_get_task(&self, task_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/tasks/{}", task_id)).await } /// Get task output/claude log. pub async fn supervisor_get_task_output(&self, task_id: Uuid) -> Result { self.get(&format!("/api/v1/mesh/tasks/{}/output", task_id)) .await } /// Mark a contract as complete. /// This will update contract status to 'completed', stop the supervisor, and clean up worktrees. pub async fn supervisor_complete(&self, contract_id: Uuid) -> Result { #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct CompleteContractRequest { status: String, } let req = CompleteContractRequest { status: "completed".to_string(), }; self.put(&format!("/api/v1/contracts/{}", contract_id), &req) .await } /// Resume a completed contract (reactivate it). /// /// This updates the contract status from 'completed' back to 'active' /// and optionally respawns the supervisor task. pub async fn supervisor_resume_contract( &self, contract_id: Uuid, ) -> Result { #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct ResumeContractRequest { status: String, } let req = ResumeContractRequest { status: "active".to_string(), }; self.put(&format!("/api/v1/contracts/{}", contract_id), &req) .await } /// Delete a task. pub async fn delete_task(&self, task_id: Uuid) -> Result<(), ApiError> { self.delete(&format!("/api/v1/mesh/tasks/{}", task_id)).await } /// Update a task. pub async fn update_task( &self, task_id: Uuid, name: Option, plan: Option, ) -> Result { #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct UpdateTaskRequest { #[serde(skip_serializing_if = "Option::is_none")] name: Option, #[serde(skip_serializing_if = "Option::is_none")] plan: Option, } let req = UpdateTaskRequest { name, plan }; self.put(&format!("/api/v1/mesh/tasks/{}", task_id), &req) .await } }