diff options
Diffstat (limited to 'makima/src/server/state.rs')
| -rw-r--r-- | makima/src/server/state.rs | 249 |
1 files changed, 6 insertions, 243 deletions
diff --git a/makima/src/server/state.rs b/makima/src/server/state.rs index e267da1..9e06b4c 100644 --- a/makima/src/server/state.rs +++ b/makima/src/server/state.rs @@ -140,10 +140,8 @@ pub struct PrResultNotification { pub struct SupervisorQuestionNotification { /// Unique ID for this question pub question_id: Uuid, - /// Supervisor task that asked the question + /// Task that asked the question pub task_id: Uuid, - /// Contract this question relates to (Uuid::nil() for directive context) - pub contract_id: Uuid, /// Directive this question relates to (if from a directive task) #[serde(skip_serializing_if = "Option::is_none")] pub directive_id: Option<Uuid>, @@ -172,7 +170,6 @@ pub struct SupervisorQuestionNotification { pub struct PendingSupervisorQuestion { pub question_id: Uuid, pub task_id: Uuid, - pub contract_id: Uuid, /// Directive this question relates to (if from a directive task) pub directive_id: Option<Uuid>, pub owner_id: Uuid, @@ -285,12 +282,6 @@ pub enum DaemonCommand { /// Files to copy from parent task's worktree #[serde(rename = "copyFiles")] copy_files: Option<Vec<String>>, - /// Contract ID if this task is associated with a contract - #[serde(rename = "contractId")] - contract_id: Option<Uuid>, - /// Whether this task is a supervisor (long-running contract orchestrator) - #[serde(rename = "isSupervisor")] - is_supervisor: bool, /// Whether to run in autonomous loop mode #[serde(rename = "autonomousLoop", default)] autonomous_loop: bool, @@ -306,15 +297,12 @@ pub enum DaemonCommand { /// Commit SHA to apply the patch on top of #[serde(rename = "patchBaseSha", default, skip_serializing_if = "Option::is_none")] patch_base_sha: Option<String>, - /// Whether the contract is in local-only mode (skips automatic completion actions) + /// Whether to skip automatic completion actions (local-only mode). #[serde(rename = "localOnly", default)] local_only: bool, /// Whether to auto-merge to target branch locally when local_only mode is enabled #[serde(rename = "autoMergeLocal", default)] auto_merge_local: bool, - /// Task ID to share worktree with (supervisor's task ID). If Some, use that task's worktree instead of creating a new one. - #[serde(rename = "supervisorWorktreeTaskId", default, skip_serializing_if = "Option::is_none")] - supervisor_worktree_task_id: Option<Uuid>, /// Directive ID if this task is associated with a directive #[serde(rename = "directiveId", default, skip_serializing_if = "Option::is_none")] directive_id: Option<Uuid>, @@ -906,29 +894,12 @@ impl AppState { let _ = self.pr_results.send(notification); } - /// Add a pending supervisor question and broadcast it. + /// Add a pending question and broadcast it. Questions live in + /// memory only; they're a back-channel for directive tasks to + /// pause for clarification (used by `makima directive ask`). pub fn add_supervisor_question( &self, task_id: Uuid, - contract_id: Uuid, - owner_id: Uuid, - question: String, - choices: Vec<String>, - context: Option<String>, - multi_select: bool, - question_type: String, - ) -> Uuid { - self.add_supervisor_question_with_directive( - task_id, contract_id, None, owner_id, - question, choices, context, multi_select, question_type, - ) - } - - /// Add a pending supervisor question with optional directive context and broadcast it. - pub fn add_supervisor_question_with_directive( - &self, - task_id: Uuid, - contract_id: Uuid, directive_id: Option<Uuid>, owner_id: Uuid, question: String, @@ -940,13 +911,11 @@ impl AppState { let question_id = Uuid::new_v4(); let now = chrono::Utc::now(); - // Store the pending question self.pending_questions.insert( question_id, PendingSupervisorQuestion { question_id, task_id, - contract_id, directive_id, owner_id, question: question.clone(), @@ -958,11 +927,9 @@ impl AppState { }, ); - // Broadcast to subscribers self.broadcast_supervisor_question(SupervisorQuestionNotification { question_id, task_id, - contract_id, directive_id, owner_id: Some(owner_id), question, @@ -976,10 +943,9 @@ impl AppState { tracing::info!( question_id = %question_id, task_id = %task_id, - contract_id = %contract_id, directive_id = ?directive_id, question_type = %question_type, - "Supervisor question added" + "Question added" ); question_id @@ -1029,7 +995,6 @@ impl AppState { self.broadcast_supervisor_question(SupervisorQuestionNotification { question_id, task_id: question.1.task_id, - contract_id: question.1.contract_id, directive_id: question.1.directive_id, owner_id: Some(question.1.owner_id), question: question.1.question, @@ -1093,38 +1058,6 @@ impl AppState { count } - /// Remove all pending questions for a specific contract. - /// - /// This should be called when a contract is deleted to clean up orphaned questions. - /// Returns the number of questions removed. - pub fn remove_pending_questions_for_contract(&self, contract_id: Uuid) -> usize { - // Collect question IDs to remove - let question_ids: Vec<Uuid> = self - .pending_questions - .iter() - .filter(|entry| entry.value().contract_id == contract_id) - .map(|entry| entry.value().question_id) - .collect(); - - let count = question_ids.len(); - - // Remove pending questions and their responses - for question_id in question_ids { - self.pending_questions.remove(&question_id); - self.question_responses.remove(&question_id); - } - - if count > 0 { - tracing::info!( - contract_id = %contract_id, - count = count, - "Cleaned up pending questions for deleted contract" - ); - } - - count - } - /// Register a new daemon connection. /// /// Returns the connection_id for later reference. @@ -1329,176 +1262,6 @@ impl AppState { .map(|entry| entry.value().clone()) } - // ========================================================================= - // Supervisor Notifications - // ========================================================================= - - /// Notify a contract's supervisor task about an event. - /// - /// This sends a message to the supervisor's stdin so it can react to changes - /// in tasks or contract state. - pub async fn notify_supervisor( - &self, - supervisor_task_id: Uuid, - supervisor_daemon_id: Option<Uuid>, - message: &str, - ) -> Result<(), String> { - // Only send if we have a daemon ID - let daemon_id = match supervisor_daemon_id { - Some(id) => id, - None => { - tracing::debug!( - supervisor_task_id = %supervisor_task_id, - "Supervisor has no daemon assigned, skipping notification" - ); - return Ok(()); - } - }; - - let command = DaemonCommand::SendMessage { - task_id: supervisor_task_id, - message: message.to_string(), - }; - - self.send_daemon_command(daemon_id, command).await - } - - /// Format and send a task completion notification to a supervisor. - /// - /// If `action_directive` is provided, it will be appended to the message - /// as an [ACTION REQUIRED] block to prompt the supervisor to take action. - pub async fn notify_supervisor_of_task_completion( - &self, - supervisor_task_id: Uuid, - supervisor_daemon_id: Option<Uuid>, - completed_task_id: Uuid, - completed_task_name: &str, - status: &str, - progress_summary: Option<&str>, - error_message: Option<&str>, - action_directive: Option<&str>, - ) { - let mut message = format!( - "TASK_COMPLETED task_id={} name=\"{}\" status={}", - completed_task_id, completed_task_name, status - ); - - if let Some(summary) = progress_summary { - // Escape newlines in summary - let escaped = summary.replace('\n', "\\n"); - message.push_str(&format!(" summary=\"{}\"", escaped)); - } - - if let Some(err) = error_message { - let escaped = err.replace('\n', "\\n"); - message.push_str(&format!(" error=\"{}\"", escaped)); - } - - // Append action directive if provided - if let Some(directive) = action_directive { - message.push_str("\n\n"); - message.push_str(directive); - } - - if let Err(e) = self.notify_supervisor( - supervisor_task_id, - supervisor_daemon_id, - &message, - ).await { - tracing::warn!( - supervisor_task_id = %supervisor_task_id, - completed_task_id = %completed_task_id, - "Failed to notify supervisor of task completion: {}", - e - ); - } - } - - /// Format and send a task status change notification to a supervisor. - pub async fn notify_supervisor_of_task_update( - &self, - supervisor_task_id: Uuid, - supervisor_daemon_id: Option<Uuid>, - updated_task_id: Uuid, - updated_task_name: &str, - new_status: &str, - updated_fields: &[String], - ) { - let message = format!( - "TASK_UPDATED task_id={} name=\"{}\" status={} fields={}", - updated_task_id, - updated_task_name, - new_status, - updated_fields.join(",") - ); - - if let Err(e) = self.notify_supervisor( - supervisor_task_id, - supervisor_daemon_id, - &message, - ).await { - tracing::warn!( - supervisor_task_id = %supervisor_task_id, - updated_task_id = %updated_task_id, - "Failed to notify supervisor of task update: {}", - e - ); - } - } - - /// Format and send a contract phase change notification to a supervisor. - pub async fn notify_supervisor_of_phase_change( - &self, - supervisor_task_id: Uuid, - supervisor_daemon_id: Option<Uuid>, - contract_id: Uuid, - new_phase: &str, - ) { - let message = format!( - "PHASE_CHANGED contract_id={} phase={}", - contract_id, new_phase - ); - - if let Err(e) = self.notify_supervisor( - supervisor_task_id, - supervisor_daemon_id, - &message, - ).await { - tracing::warn!( - supervisor_task_id = %supervisor_task_id, - contract_id = %contract_id, - "Failed to notify supervisor of phase change: {}", - e - ); - } - } - - /// Format and send a new task created notification to a supervisor. - pub async fn notify_supervisor_of_task_created( - &self, - supervisor_task_id: Uuid, - supervisor_daemon_id: Option<Uuid>, - new_task_id: Uuid, - new_task_name: &str, - ) { - let message = format!( - "TASK_CREATED task_id={} name=\"{}\"", - new_task_id, new_task_name - ); - - if let Err(e) = self.notify_supervisor( - supervisor_task_id, - supervisor_daemon_id, - &message, - ).await { - tracing::warn!( - supervisor_task_id = %supervisor_task_id, - new_task_id = %new_task_id, - "Failed to notify supervisor of task creation: {}", - e - ); - } - } } /// Type alias for the shared application state. |
