summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/handlers')
-rw-r--r--makima/src/server/handlers/contract_chat.rs12
-rw-r--r--makima/src/server/handlers/contracts.rs4
-rw-r--r--makima/src/server/handlers/mesh.rs46
-rw-r--r--makima/src/server/handlers/mesh_chat.rs11
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs92
-rw-r--r--makima/src/server/handlers/mesh_supervisor.rs13
-rw-r--r--makima/src/server/handlers/transcript_analysis.rs1
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 {