summaryrefslogtreecommitdiff
path: root/makima/src/server/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/state.rs')
-rw-r--r--makima/src/server/state.rs249
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.