diff options
Diffstat (limited to 'makima/src/server/handlers')
| -rw-r--r-- | makima/src/server/handlers/contract_chat.rs | 12 | ||||
| -rw-r--r-- | makima/src/server/handlers/contracts.rs | 4 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh.rs | 46 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_chat.rs | 11 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_daemon.rs | 92 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_supervisor.rs | 13 | ||||
| -rw-r--r-- | makima/src/server/handlers/transcript_analysis.rs | 1 |
7 files changed, 177 insertions, 2 deletions
diff --git a/makima/src/server/handlers/contract_chat.rs b/makima/src/server/handlers/contract_chat.rs index e035368..e6ee8d4 100644 --- a/makima/src/server/handlers/contract_chat.rs +++ b/makima/src/server/handlers/contract_chat.rs @@ -1567,6 +1567,16 @@ async fn handle_contract_request( } }; + // Get local_only from contract if task has one + let local_only = if let Some(contract_id) = task.contract_id { + match repository::get_contract_for_owner(pool, contract_id, owner_id).await { + Ok(Some(contract)) => contract.local_only, + _ => false, + } + } else { + false + }; + // Send SpawnTask command to daemon let command = DaemonCommand::SpawnTask { task_id, @@ -1589,6 +1599,7 @@ async fn handle_contract_request( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only, }; if let Err(e) = command_sender.send(command).await { @@ -2574,6 +2585,7 @@ async fn handle_contract_request( initial_phase: Some("research".to_string()), autonomous_loop: None, phase_guard: None, + local_only: None, }; let contract = match repository::create_contract_for_owner(pool, owner_id, contract_req).await { diff --git a/makima/src/server/handlers/contracts.rs b/makima/src/server/handlers/contracts.rs index de3164c..3498063 100644 --- a/makima/src/server/handlers/contracts.rs +++ b/makima/src/server/handlers/contracts.rs @@ -366,6 +366,7 @@ pub async fn create_contract( phase: contract.phase, status: contract.status, supervisor_task_id: contract.supervisor_task_id, + local_only: contract.local_only, file_count: 0, task_count: 0, repository_count: 0, @@ -387,6 +388,7 @@ pub async fn create_contract( phase: contract.phase, status: contract.status, supervisor_task_id: contract.supervisor_task_id, + local_only: contract.local_only, file_count: 0, task_count: 0, repository_count: 0, @@ -515,6 +517,7 @@ pub async fn update_contract( phase: contract.phase, status: contract.status, supervisor_task_id: contract.supervisor_task_id, + local_only: contract.local_only, file_count: 0, task_count: 0, repository_count: 0, @@ -1399,6 +1402,7 @@ pub async fn change_phase( phase: updated_contract.phase, status: updated_contract.status, supervisor_task_id: updated_contract.supervisor_task_id, + local_only: updated_contract.local_only, file_count: 0, task_count: 0, repository_count: 0, diff --git a/makima/src/server/handlers/mesh.rs b/makima/src/server/handlers/mesh.rs index 545d1ea..19958e7 100644 --- a/makima/src/server/handlers/mesh.rs +++ b/makima/src/server/handlers/mesh.rs @@ -599,6 +599,16 @@ pub async fn start_task( .into_response(); } + // Get local_only flag from contract if task has one + let local_only = if let Some(contract_id) = task.contract_id { + match repository::get_contract_for_owner(pool, contract_id, auth.owner_id).await { + Ok(Some(contract)) => contract.local_only, + _ => false, + } + } else { + false + }; + // Get list of daemons that have previously failed this task let mut exclude_daemon_ids: Vec<Uuid> = task.failed_daemon_ids.clone().unwrap_or_default(); @@ -694,6 +704,7 @@ pub async fn start_task( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only, }; tracing::info!( @@ -746,6 +757,7 @@ pub async fn start_task( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only, }; if state.send_daemon_command(alt_daemon_id, alt_command).await.is_ok() { @@ -1128,6 +1140,16 @@ pub async fn send_message( }; if let Ok(Some(updated_task)) = repository::update_task_for_owner(pool, id, auth.owner_id, update_req).await { + // Get local_only from contract if task has one + let local_only = if let Some(contract_id) = updated_task.contract_id { + match repository::get_contract_for_owner(pool, contract_id, auth.owner_id).await { + Ok(Some(contract)) => contract.local_only, + _ => false, + } + } else { + false + }; + // Send spawn command to new daemon let spawn_cmd = DaemonCommand::SpawnTask { task_id: id, @@ -1150,6 +1172,7 @@ pub async fn send_message( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only, }; if state.send_daemon_command(new_daemon_id, spawn_cmd).await.is_ok() { @@ -2293,6 +2316,16 @@ pub async fn reassign_task( } }; + // Get local_only from contract if task has one + let local_only = if let Some(contract_id) = task.contract_id { + match repository::get_contract_for_owner(pool, contract_id, auth.owner_id).await { + Ok(Some(contract)) => contract.local_only, + _ => false, + } + } else { + false + }; + // Send SpawnTask command to daemon for the new task let command = DaemonCommand::SpawnTask { task_id: new_task.id, @@ -2315,6 +2348,7 @@ pub async fn reassign_task( conversation_history: None, patch_data, patch_base_sha, + local_only, }; tracing::info!( @@ -2620,6 +2654,16 @@ pub async fn continue_task( }; let is_orchestrator = task.depth == 0 && subtask_count > 0; + // Get local_only from contract if task has one + let local_only = if let Some(contract_id) = task.contract_id { + match repository::get_contract_for_owner(pool, contract_id, auth.owner_id).await { + Ok(Some(contract)) => contract.local_only, + _ => false, + } + } else { + false + }; + // Send SpawnTask command to daemon let command = DaemonCommand::SpawnTask { task_id: id, @@ -2642,6 +2686,7 @@ pub async fn continue_task( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only, }; tracing::info!( @@ -3562,6 +3607,7 @@ pub async fn branch_task( conversation_history: updated_task.conversation_state.clone(), patch_data, patch_base_sha, + local_only: false, // No contract, so not local_only }; if let Err(e) = state.send_daemon_command(target_daemon_id, command).await { diff --git a/makima/src/server/handlers/mesh_chat.rs b/makima/src/server/handlers/mesh_chat.rs index 1ff0724..eb35728 100644 --- a/makima/src/server/handlers/mesh_chat.rs +++ b/makima/src/server/handlers/mesh_chat.rs @@ -1131,6 +1131,16 @@ async fn handle_mesh_request( } }; + // Get local_only from contract if task has one + let local_only = if let Some(contract_id) = task.contract_id { + match repository::get_contract_for_owner(pool, contract_id, owner_id).await { + Ok(Some(contract)) => contract.local_only, + _ => false, + } + } else { + false + }; + // Send SpawnTask command to daemon let command = DaemonCommand::SpawnTask { task_id, @@ -1153,6 +1163,7 @@ async fn handle_mesh_request( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only, }; match state.send_daemon_command(target_daemon_id, command).await { diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs index 0ba37d2..0aea40e 100644 --- a/makima/src/server/handlers/mesh_daemon.rs +++ b/makima/src/server/handlers/mesh_daemon.rs @@ -448,6 +448,29 @@ pub enum DaemonMessage { /// Error message if operation failed error: Option<String>, }, + /// Response to CreateExportPatch command + ExportPatchCreated { + #[serde(rename = "taskId")] + task_id: Uuid, + success: bool, + /// The uncompressed, human-readable patch content + #[serde(rename = "patchContent")] + patch_content: Option<String>, + /// Number of files changed + #[serde(rename = "filesCount")] + files_count: Option<usize>, + /// Lines added + #[serde(rename = "linesAdded")] + lines_added: Option<usize>, + /// Lines removed + #[serde(rename = "linesRemoved")] + lines_removed: Option<usize>, + /// The base commit SHA that the patch is diffed against + #[serde(rename = "baseCommitSha")] + base_commit_sha: Option<String>, + /// Error message if failed + error: Option<String>, + }, /// Response to MergeTaskToTarget command MergeToTargetResult { #[serde(rename = "taskId")] @@ -1778,6 +1801,75 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re ); } } + Ok(DaemonMessage::ExportPatchCreated { + task_id, + success, + patch_content, + files_count, + lines_added, + lines_removed, + base_commit_sha, + error, + }) => { + if success { + tracing::info!( + task_id = %task_id, + files_count = ?files_count, + lines_added = ?lines_added, + lines_removed = ?lines_removed, + base_commit_sha = ?base_commit_sha, + patch_len = patch_content.as_ref().map(|p| p.len()), + "Export patch created successfully" + ); + + // Broadcast as task output so UI can access the result + let output_text = format!( + "✓ Export patch created: {} files changed, +{} -{} lines (base: {})", + files_count.unwrap_or(0), + lines_added.unwrap_or(0), + lines_removed.unwrap_or(0), + base_commit_sha.as_deref().unwrap_or("unknown") + ); + state.broadcast_task_output(TaskOutputNotification { + task_id, + owner_id: Some(owner_id), + message_type: "export_patch".to_string(), + content: output_text, + tool_name: None, + tool_input: Some(serde_json::json!({ + "patchContent": patch_content, + "filesCount": files_count, + "linesAdded": lines_added, + "linesRemoved": lines_removed, + "baseCommitSha": base_commit_sha, + })), + is_error: None, + cost_usd: None, + duration_ms: None, + is_partial: false, + }); + } else { + tracing::warn!( + task_id = %task_id, + error = ?error, + "Failed to create export patch" + ); + + // Broadcast error + state.broadcast_task_output(TaskOutputNotification { + task_id, + owner_id: Some(owner_id), + message_type: "error".to_string(), + content: format!("✗ Export patch failed: {}", error.unwrap_or_else(|| "Unknown error".to_string())), + tool_name: None, + tool_input: None, + is_error: Some(true), + cost_usd: None, + duration_ms: None, + is_partial: false, + }); + } + } Err(e) => { tracing::warn!("Failed to parse daemon message: {}", e); } diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs index d1a1a99..a654a05 100644 --- a/makima/src/server/handlers/mesh_supervisor.rs +++ b/makima/src/server/handlers/mesh_supervisor.rs @@ -297,6 +297,12 @@ pub async fn try_start_pending_task( return Ok(None); } + // Get contract to check local_only flag + let contract = repository::get_contract_for_owner(pool, contract_id, owner_id) + .await + .map_err(|e| format!("Failed to get contract: {}", e))? + .ok_or_else(|| "Contract not found".to_string())?; + // Try each pending task until we find one we can start for task in &pending_tasks { // Get excluded daemon IDs for this task (daemons that have already failed it) @@ -399,6 +405,7 @@ pub async fn try_start_pending_task( conversation_history: None, patch_data, patch_base_sha, + local_only: contract.local_only, }; if let Err(e) = state.send_daemon_command(daemon.id, cmd).await { @@ -532,8 +539,8 @@ pub async fn spawn_task( let pool = state.db_pool.as_ref().unwrap(); - // Verify contract exists - let _contract = match repository::get_contract_for_owner(pool, request.contract_id, owner_id).await { + // Verify contract exists and get local_only flag + let contract = match repository::get_contract_for_owner(pool, request.contract_id, owner_id).await { Ok(Some(c)) => c, Ok(None) => { return ( @@ -711,6 +718,7 @@ pub async fn spawn_task( conversation_history: None, patch_data: None, patch_base_sha: None, + local_only: contract.local_only, }; if let Err(e) = state.send_daemon_command(daemon.id, cmd).await { @@ -2133,6 +2141,7 @@ pub async fn resume_supervisor( conversation_history: Some(supervisor_state.conversation_history.clone()), // Fallback if worktree missing patch_data, patch_base_sha, + local_only: contract.local_only, }; if let Err(e) = state.send_daemon_command(target_daemon_id, command).await { diff --git a/makima/src/server/handlers/transcript_analysis.rs b/makima/src/server/handlers/transcript_analysis.rs index 3b71eca..8eb50c7 100644 --- a/makima/src/server/handlers/transcript_analysis.rs +++ b/makima/src/server/handlers/transcript_analysis.rs @@ -278,6 +278,7 @@ pub async fn create_contract_from_analysis( initial_phase: Some("research".to_string()), autonomous_loop: None, phase_guard: None, + local_only: None, }; let contract = match repository::create_contract_for_owner(pool, auth.owner_id, contract_req).await { |
