summaryrefslogtreecommitdiff
path: root/makima/src/db/models.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/db/models.rs')
-rw-r--r--makima/src/db/models.rs603
1 files changed, 601 insertions, 2 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 5064b97..e16c43f 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -60,6 +60,8 @@ pub enum BodyElement {
alt: Option<String>,
caption: Option<String>,
},
+ /// Raw markdown content - renders as formatted markdown, edits as raw text
+ Markdown { content: String },
}
/// File record from the database.
@@ -68,6 +70,10 @@ pub enum BodyElement {
pub struct File {
pub id: Uuid,
pub owner_id: Uuid,
+ /// Contract this file belongs to (optional)
+ pub contract_id: Option<Uuid>,
+ /// Phase of the contract when file was added (e.g., "research", "specify")
+ pub contract_phase: Option<String>,
pub name: String,
pub description: Option<String>,
#[sqlx(json)]
@@ -80,6 +86,12 @@ pub struct File {
pub body: Vec<BodyElement>,
/// Version number for optimistic locking
pub version: i32,
+ /// Path to linked repository file (e.g., "README.md", "docs/design.md")
+ pub repo_file_path: Option<String>,
+ /// When the file was last synced from the repository
+ pub repo_synced_at: Option<DateTime<Utc>>,
+ /// Sync status: 'none', 'synced', 'modified', 'conflict'
+ pub repo_sync_status: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -88,14 +100,24 @@ pub struct File {
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateFileRequest {
+ /// Contract this file belongs to (required - files must belong to a contract)
+ pub contract_id: Uuid,
/// Name of the file (auto-generated if not provided)
pub name: Option<String>,
/// Optional description
pub description: Option<String>,
- /// Transcript entries
+ /// Transcript entries (default to empty)
+ #[serde(default)]
pub transcript: Vec<TranscriptEntry>,
/// Storage location (e.g., s3://bucket/path) - not used yet
pub location: Option<String>,
+ /// Initial body elements (e.g., from a template)
+ #[serde(default)]
+ pub body: Vec<BodyElement>,
+ /// Path to linked repository file (e.g., "README.md")
+ pub repo_file_path: Option<String>,
+ /// Contract phase this file belongs to (for deliverable tracking)
+ pub contract_phase: Option<String>,
}
/// Request payload for updating an existing file.
@@ -114,6 +136,8 @@ pub struct UpdateFileRequest {
pub body: Option<Vec<BodyElement>>,
/// Version for optimistic locking (required for updates from frontend)
pub version: Option<i32>,
+ /// Path to linked repository file (e.g., "README.md")
+ pub repo_file_path: Option<String>,
}
/// Response for file list endpoint.
@@ -129,6 +153,12 @@ pub struct FileListResponse {
#[serde(rename_all = "camelCase")]
pub struct FileSummary {
pub id: Uuid,
+ /// Contract this file belongs to
+ pub contract_id: Option<Uuid>,
+ /// Contract name (joined from contracts table)
+ pub contract_name: Option<String>,
+ /// Phase when file was added to contract
+ pub contract_phase: Option<String>,
pub name: String,
pub description: Option<String>,
pub transcript_count: usize,
@@ -136,6 +166,10 @@ pub struct FileSummary {
pub duration: Option<f32>,
/// Version number for optimistic locking
pub version: i32,
+ /// Path to linked repository file
+ pub repo_file_path: Option<String>,
+ /// Sync status with repository
+ pub repo_sync_status: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -149,11 +183,16 @@ impl From<File> for FileSummary {
.fold(0.0_f32, f32::max);
Self {
id: file.id,
+ contract_id: file.contract_id,
+ contract_name: None, // Not available from File alone, requires JOIN
+ contract_phase: file.contract_phase,
name: file.name,
description: file.description,
transcript_count: file.transcript.len(),
duration: if duration > 0.0 { Some(duration) } else { None },
version: file.version,
+ repo_file_path: file.repo_file_path,
+ repo_sync_status: file.repo_sync_status,
created_at: file.created_at,
updated_at: file.updated_at,
}
@@ -345,8 +384,10 @@ impl std::str::FromStr for MergeMode {
pub struct Task {
pub id: Uuid,
pub owner_id: Uuid,
+ /// Contract this task belongs to (required for new tasks)
+ pub contract_id: Option<Uuid>,
pub parent_task_id: Option<Uuid>,
- /// Depth in task hierarchy: 0=orchestrator (top-level), 1=subtask (max)
+ /// Depth in task hierarchy (no longer constrained)
pub depth: i32,
pub name: String,
pub description: Option<String>,
@@ -354,6 +395,11 @@ pub struct Task {
pub priority: i32,
pub plan: String,
+ // Supervisor flag
+ /// True for contract supervisor tasks. Only supervisors can spawn new tasks.
+ #[serde(default)]
+ pub is_supervisor: bool,
+
// Daemon/container info
pub daemon_id: Option<Uuid>,
pub container_id: Option<String>,
@@ -379,6 +425,30 @@ pub struct Task {
pub last_output: Option<String>,
pub error_message: Option<String>,
+ // Git checkpoint tracking
+ /// Git commit SHA of the most recent checkpoint
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub last_checkpoint_sha: Option<String>,
+ /// Number of checkpoints created by this task
+ #[serde(default)]
+ pub checkpoint_count: i32,
+ /// Message from the most recent checkpoint
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub checkpoint_message: Option<String>,
+
+ // Conversation state for resumption
+ /// Saved conversation context for task/supervisor resumption
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub conversation_state: Option<serde_json::Value>,
+
+ // Daemon migration tracking
+ /// Previous daemon if task was migrated
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub migrated_from_daemon_id: Option<Uuid>,
+ /// Most recent daemon that worked on this task
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub last_active_daemon_id: Option<Uuid>,
+
// Timestamps
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
@@ -413,6 +483,12 @@ impl Task {
#[serde(rename_all = "camelCase")]
pub struct TaskSummary {
pub id: Uuid,
+ /// Contract this task belongs to
+ pub contract_id: Option<Uuid>,
+ /// Contract name (joined from contracts table)
+ pub contract_name: Option<String>,
+ /// Contract phase (joined from contracts table)
+ pub contract_phase: Option<String>,
pub parent_task_id: Option<Uuid>,
/// Depth in task hierarchy: 0=orchestrator (top-level), 1=subtask (max)
pub depth: i32,
@@ -422,10 +498,36 @@ pub struct TaskSummary {
pub progress_summary: Option<String>,
pub subtask_count: i64,
pub version: i32,
+ /// True for contract supervisor tasks
+ #[serde(default)]
+ pub is_supervisor: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
+/// Convert a full Task to a TaskSummary
+impl From<Task> for TaskSummary {
+ fn from(task: Task) -> Self {
+ Self {
+ id: task.id,
+ contract_id: task.contract_id,
+ contract_name: None, // Not available from Task directly
+ contract_phase: None, // Not available from Task directly
+ parent_task_id: task.parent_task_id,
+ depth: task.depth,
+ name: task.name,
+ status: task.status,
+ priority: task.priority,
+ progress_summary: task.progress_summary,
+ subtask_count: 0, // Would need separate query
+ version: task.version,
+ is_supervisor: task.is_supervisor,
+ created_at: task.created_at,
+ updated_at: task.updated_at,
+ }
+ }
+}
+
/// Response for task list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
@@ -438,6 +540,8 @@ pub struct TaskListResponse {
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateTaskRequest {
+ /// Contract this task belongs to (required)
+ pub contract_id: Uuid,
/// Name of the task
pub name: String,
/// Optional description
@@ -446,6 +550,9 @@ pub struct CreateTaskRequest {
pub plan: String,
/// Parent task ID (for subtasks)
pub parent_task_id: Option<Uuid>,
+ /// True for contract supervisor tasks. Only supervisors can spawn new tasks.
+ #[serde(default)]
+ pub is_supervisor: bool,
/// Priority (higher = more urgent)
#[serde(default)]
pub priority: i32,
@@ -466,6 +573,8 @@ pub struct CreateTaskRequest {
/// Files to copy from parent task's worktree when starting
#[serde(skip_serializing_if = "Option::is_none")]
pub copy_files: Option<Vec<String>>,
+ /// Checkpoint SHA to branch from (optional)
+ pub checkpoint_sha: Option<String>,
}
/// Request payload for updating a task
@@ -482,6 +591,8 @@ pub struct UpdateTaskRequest {
pub error_message: Option<String>,
pub merge_mode: Option<String>,
pub pr_url: Option<String>,
+ /// Repository URL for the task (e.g., when updating supervisor with repo info)
+ pub repository_url: Option<String>,
/// Path to user's local repository (outside ~/.makima)
pub target_repo_path: Option<String>,
/// Action on completion: "none", "branch", "merge", "pr"
@@ -733,6 +844,47 @@ pub struct MeshChatHistoryResponse {
}
// =============================================================================
+// Contract Chat History Types
+// =============================================================================
+
+/// Conversation thread for contract chat (scoped to a specific contract)
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractChatConversation {
+ pub id: Uuid,
+ pub contract_id: Uuid,
+ pub owner_id: Uuid,
+ pub name: Option<String>,
+ pub is_active: bool,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+/// Individual message in a contract chat conversation
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractChatMessageRecord {
+ pub id: Uuid,
+ pub conversation_id: Uuid,
+ pub role: String,
+ pub content: String,
+ /// Tool calls made during this message (JSON, nullable)
+ pub tool_calls: Option<serde_json::Value>,
+ /// Pending questions requiring user response (JSON, nullable)
+ pub pending_questions: Option<serde_json::Value>,
+ pub created_at: DateTime<Utc>,
+}
+
+/// Response for contract chat history endpoint
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractChatHistoryResponse {
+ pub contract_id: Uuid,
+ pub conversation_id: Uuid,
+ pub messages: Vec<ContractChatMessageRecord>,
+}
+
+// =============================================================================
// Merge API Types
// =============================================================================
@@ -834,3 +986,450 @@ pub struct MergeCompleteCheckResponse {
/// Count of skipped branches
pub skipped_count: u32,
}
+
+// =============================================================================
+// Contract Types
+// =============================================================================
+
+/// Contract phase for workflow progression
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum ContractPhase {
+ Research,
+ Specify,
+ Plan,
+ Execute,
+ Review,
+}
+
+impl std::fmt::Display for ContractPhase {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ContractPhase::Research => write!(f, "research"),
+ ContractPhase::Specify => write!(f, "specify"),
+ ContractPhase::Plan => write!(f, "plan"),
+ ContractPhase::Execute => write!(f, "execute"),
+ ContractPhase::Review => write!(f, "review"),
+ }
+ }
+}
+
+impl std::str::FromStr for ContractPhase {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "research" => Ok(ContractPhase::Research),
+ "specify" => Ok(ContractPhase::Specify),
+ "plan" => Ok(ContractPhase::Plan),
+ "execute" => Ok(ContractPhase::Execute),
+ "review" => Ok(ContractPhase::Review),
+ _ => Err(format!("Unknown contract phase: {}", s)),
+ }
+ }
+}
+
+/// Contract status
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum ContractStatus {
+ Active,
+ Completed,
+ Archived,
+}
+
+impl std::fmt::Display for ContractStatus {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ContractStatus::Active => write!(f, "active"),
+ ContractStatus::Completed => write!(f, "completed"),
+ ContractStatus::Archived => write!(f, "archived"),
+ }
+ }
+}
+
+impl std::str::FromStr for ContractStatus {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "active" => Ok(ContractStatus::Active),
+ "completed" => Ok(ContractStatus::Completed),
+ "archived" => Ok(ContractStatus::Archived),
+ _ => Err(format!("Unknown contract status: {}", s)),
+ }
+ }
+}
+
+/// Repository source type
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum RepositorySourceType {
+ /// Existing remote repo (GitHub, GitLab, etc)
+ Remote,
+ /// Existing local repo
+ Local,
+ /// New repo created/managed by Makima daemon
+ Managed,
+}
+
+impl std::fmt::Display for RepositorySourceType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ RepositorySourceType::Remote => write!(f, "remote"),
+ RepositorySourceType::Local => write!(f, "local"),
+ RepositorySourceType::Managed => write!(f, "managed"),
+ }
+ }
+}
+
+impl std::str::FromStr for RepositorySourceType {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "remote" => Ok(RepositorySourceType::Remote),
+ "local" => Ok(RepositorySourceType::Local),
+ "managed" => Ok(RepositorySourceType::Managed),
+ _ => Err(format!("Unknown repository source type: {}", s)),
+ }
+ }
+}
+
+/// Repository status (for managed repos)
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum RepositoryStatus {
+ /// Repo is usable
+ Ready,
+ /// Waiting for daemon to create
+ Pending,
+ /// Daemon is creating the repo
+ Creating,
+ /// Creation failed
+ Failed,
+}
+
+impl std::fmt::Display for RepositoryStatus {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ RepositoryStatus::Ready => write!(f, "ready"),
+ RepositoryStatus::Pending => write!(f, "pending"),
+ RepositoryStatus::Creating => write!(f, "creating"),
+ RepositoryStatus::Failed => write!(f, "failed"),
+ }
+ }
+}
+
+impl std::str::FromStr for RepositoryStatus {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "ready" => Ok(RepositoryStatus::Ready),
+ "pending" => Ok(RepositoryStatus::Pending),
+ "creating" => Ok(RepositoryStatus::Creating),
+ "failed" => Ok(RepositoryStatus::Failed),
+ _ => Err(format!("Unknown repository status: {}", s)),
+ }
+ }
+}
+
+/// Contract record from the database
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct Contract {
+ pub id: Uuid,
+ pub owner_id: Uuid,
+ pub name: String,
+ pub description: Option<String>,
+ pub phase: String,
+ pub status: String,
+ /// The long-running supervisor task that orchestrates this contract
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub supervisor_task_id: Option<Uuid>,
+ pub version: i32,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+impl Contract {
+ /// Parse phase string to ContractPhase enum
+ pub fn phase_enum(&self) -> Result<ContractPhase, String> {
+ self.phase.parse()
+ }
+
+ /// Parse status string to ContractStatus enum
+ pub fn status_enum(&self) -> Result<ContractStatus, String> {
+ self.status.parse()
+ }
+}
+
+/// Contract repository record from the database
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractRepository {
+ pub id: Uuid,
+ pub contract_id: Uuid,
+ pub name: String,
+ pub repository_url: Option<String>,
+ pub local_path: Option<String>,
+ pub source_type: String,
+ pub status: String,
+ pub is_primary: bool,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+impl ContractRepository {
+ /// Parse source_type string to RepositorySourceType enum
+ pub fn source_type_enum(&self) -> Result<RepositorySourceType, String> {
+ self.source_type.parse()
+ }
+
+ /// Parse status string to RepositoryStatus enum
+ pub fn status_enum(&self) -> Result<RepositoryStatus, String> {
+ self.status.parse()
+ }
+}
+
+/// Summary of a contract for list views
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractSummary {
+ pub id: Uuid,
+ pub name: String,
+ pub description: Option<String>,
+ pub phase: String,
+ pub status: String,
+ pub file_count: i64,
+ pub task_count: i64,
+ pub repository_count: i64,
+ pub version: i32,
+ pub created_at: DateTime<Utc>,
+}
+
+/// Contract with all relations for detail view
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractWithRelations {
+ #[serde(flatten)]
+ pub contract: Contract,
+ pub repositories: Vec<ContractRepository>,
+ pub files: Vec<FileSummary>,
+ pub tasks: Vec<TaskSummary>,
+}
+
+/// Response for contract list endpoint
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractListResponse {
+ pub contracts: Vec<ContractSummary>,
+ pub total: i64,
+}
+
+/// Request payload for creating a new contract
+#[derive(Debug, Clone, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateContractRequest {
+ /// Name of the contract
+ pub name: String,
+ /// Optional description
+ pub description: Option<String>,
+ /// Initial phase to start in (defaults to "research")
+ /// Valid values: "research", "specify", "plan", "execute", "review"
+ #[serde(default)]
+ pub initial_phase: Option<String>,
+}
+
+/// Request payload for updating a contract
+#[derive(Debug, Default, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateContractRequest {
+ pub name: Option<String>,
+ pub description: Option<String>,
+ pub phase: Option<String>,
+ pub status: Option<String>,
+ /// Supervisor task ID for contract orchestration
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub supervisor_task_id: Option<Uuid>,
+ /// Version for optimistic locking
+ pub version: Option<i32>,
+}
+
+/// Request to add a remote repository to a contract
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct AddRemoteRepositoryRequest {
+ pub name: String,
+ pub repository_url: String,
+ #[serde(default)]
+ pub is_primary: bool,
+}
+
+/// Request to add a local repository to a contract
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct AddLocalRepositoryRequest {
+ pub name: String,
+ pub local_path: String,
+ #[serde(default)]
+ pub is_primary: bool,
+}
+
+/// Request to create a managed repository (daemon will create it)
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateManagedRepositoryRequest {
+ pub name: String,
+ #[serde(default)]
+ pub is_primary: bool,
+}
+
+/// Request to change contract phase
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ChangePhaseRequest {
+ pub phase: String,
+}
+
+/// Contract event record from the database
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractEvent {
+ pub id: Uuid,
+ pub contract_id: Uuid,
+ pub event_type: String,
+ pub previous_phase: Option<String>,
+ pub new_phase: Option<String>,
+ #[sqlx(json)]
+ pub event_data: Option<serde_json::Value>,
+ pub created_at: DateTime<Utc>,
+}
+
+/// Response for contract events list endpoint
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractEventListResponse {
+ pub events: Vec<ContractEvent>,
+ pub total: i64,
+}
+
+// ============================================================================
+// Task Checkpoints (for git checkpoint tracking)
+// ============================================================================
+
+/// Task checkpoint record - represents a git commit checkpoint
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct TaskCheckpoint {
+ pub id: Uuid,
+ pub task_id: Uuid,
+ /// Sequential checkpoint number within this task
+ pub checkpoint_number: i32,
+ /// Git commit SHA
+ pub commit_sha: String,
+ /// Git branch name
+ pub branch_name: String,
+ /// Commit message
+ pub message: String,
+ /// Files changed in this commit: [{path, action: 'A'|'M'|'D'}]
+ #[sqlx(json)]
+ pub files_changed: Option<serde_json::Value>,
+ /// Lines added in this commit
+ pub lines_added: Option<i32>,
+ /// Lines removed in this commit
+ pub lines_removed: Option<i32>,
+ pub created_at: DateTime<Utc>,
+}
+
+/// Request to create a checkpoint
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateCheckpointRequest {
+ /// Commit message
+ pub message: String,
+}
+
+/// Response for checkpoint list endpoint
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CheckpointListResponse {
+ pub checkpoints: Vec<TaskCheckpoint>,
+ pub total: i64,
+}
+
+// ============================================================================
+// Supervisor State (for supervisor resumability)
+// ============================================================================
+
+/// Supervisor state for contract supervisor tasks
+/// Enables resumption after interruption
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct SupervisorState {
+ pub id: Uuid,
+ pub contract_id: Uuid,
+ pub task_id: Uuid,
+ /// Full Claude conversation history for resumption
+ #[sqlx(json)]
+ pub conversation_history: serde_json::Value,
+ /// Last checkpoint this supervisor created
+ pub last_checkpoint_id: Option<Uuid>,
+ /// Tasks the supervisor is waiting on
+ #[sqlx(try_from = "Vec<Uuid>")]
+ pub pending_task_ids: Vec<Uuid>,
+ /// Current contract phase when supervisor was last active
+ pub phase: String,
+ /// When supervisor was last active
+ pub last_activity: DateTime<Utc>,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+/// Request to update supervisor state
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateSupervisorStateRequest {
+ /// Updated conversation history
+ pub conversation_history: Option<serde_json::Value>,
+ /// Tasks the supervisor is waiting on
+ pub pending_task_ids: Option<Vec<Uuid>>,
+ /// Current contract phase
+ pub phase: Option<String>,
+}
+
+// ============================================================================
+// Daemon Task Assignments (for multi-daemon support)
+// ============================================================================
+
+/// Daemon task assignment record
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DaemonTaskAssignment {
+ pub id: Uuid,
+ pub daemon_id: Uuid,
+ pub task_id: Uuid,
+ pub assigned_at: DateTime<Utc>,
+ /// Status: 'active', 'migrating', 'completed'
+ pub status: String,
+}
+
+/// Extended daemon info for selection
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DaemonWithCapacity {
+ pub id: Uuid,
+ pub owner_id: Uuid,
+ pub connection_id: String,
+ pub hostname: Option<String>,
+ pub machine_id: Option<String>,
+ pub max_concurrent_tasks: i32,
+ pub current_task_count: i32,
+ pub capacity_score: Option<i32>,
+ pub task_queue_length: Option<i32>,
+ pub supports_migration: Option<bool>,
+ pub status: String,
+ pub last_heartbeat_at: DateTime<Utc>,
+ pub connected_at: DateTime<Utc>,
+}