//! 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<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checkpoint_sha: Option<String>,
}
#[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<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MergeRequest {
pub squash: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_branch: Option<String>,
}
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<String>,
pub timeout_seconds: i32,
/// When true, the request will block indefinitely until user responds (no timeout)
pub phaseguard: bool,
}
// 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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<String>,
) -> Result<JsonValue, ApiError> {
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<String>,
squash: bool,
) -> Result<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
self.get(&format!("/api/v1/mesh/tasks/{}/checkpoints", task_id))
.await
}
/// Get contract status.
pub async fn supervisor_status(&self, contract_id: Uuid) -> Result<JsonValue, ApiError> {
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<String>,
context: Option<String>,
timeout_seconds: i32,
phaseguard: bool,
) -> Result<JsonValue, ApiError> {
let req = AskQuestionRequest {
question: question.to_string(),
choices,
context,
timeout_seconds,
phaseguard,
};
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<JsonValue, ApiError> {
#[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<JsonValue, ApiError> {
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<JsonValue, ApiError> {
self.get(&format!("/api/v1/mesh/tasks/{}/output", task_id))
.await
}
}