summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/mesh.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/handlers/mesh.rs')
-rw-r--r--makima/src/server/handlers/mesh.rs158
1 files changed, 158 insertions, 0 deletions
diff --git a/makima/src/server/handlers/mesh.rs b/makima/src/server/handlers/mesh.rs
index c4d862c..4b82ed7 100644
--- a/makima/src/server/handlers/mesh.rs
+++ b/makima/src/server/handlers/mesh.rs
@@ -705,6 +705,7 @@ pub async fn start_task(
patch_data: None,
patch_base_sha: None,
local_only,
+ supervisor_worktree_task_id: None, // Not spawned by supervisor
};
tracing::info!(
@@ -758,6 +759,7 @@ pub async fn start_task(
patch_data: None,
patch_base_sha: None,
local_only,
+ supervisor_worktree_task_id: None, // Not spawned by supervisor
};
if state.send_daemon_command(alt_daemon_id, alt_command).await.is_ok() {
@@ -1173,6 +1175,7 @@ pub async fn send_message(
patch_data: None,
patch_base_sha: None,
local_only,
+ supervisor_worktree_task_id: None, // Not spawned by supervisor
};
if state.send_daemon_command(new_daemon_id, spawn_cmd).await.is_ok() {
@@ -1890,6 +1893,158 @@ pub async fn clone_worktree(
.into_response()
}
+// =============================================================================
+// Worktree Info
+// =============================================================================
+
+/// Response for worktree info.
+#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct WorktreeInfoResponse {
+ /// Task ID.
+ pub task_id: Uuid,
+ /// Path to the worktree directory.
+ pub worktree_path: Option<String>,
+ /// Whether the worktree exists.
+ pub exists: bool,
+ /// Aggregate statistics.
+ pub stats: WorktreeStats,
+ /// Changed files list.
+ pub files: Vec<WorktreeFile>,
+ /// Current branch name.
+ pub branch: Option<String>,
+ /// Current HEAD commit SHA.
+ pub head_sha: Option<String>,
+}
+
+/// Statistics about worktree changes.
+#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct WorktreeStats {
+ /// Number of files changed.
+ pub files_changed: i32,
+ /// Total lines inserted.
+ pub insertions: i32,
+ /// Total lines deleted.
+ pub deletions: i32,
+}
+
+/// Information about a changed file in the worktree.
+#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct WorktreeFile {
+ /// File path relative to worktree root.
+ pub path: String,
+ /// Git status code (M, A, D, R, C, U, ?).
+ pub status: String,
+ /// Lines added.
+ pub lines_added: i32,
+ /// Lines removed.
+ pub lines_removed: i32,
+}
+
+/// Get worktree information for a task (files, stats, branch info).
+#[utoipa::path(
+ get,
+ path = "/api/v1/mesh/tasks/{id}/worktree-info",
+ params(
+ ("id" = Uuid, Path, description = "Task ID")
+ ),
+ responses(
+ (status = 200, description = "Worktree info", body = WorktreeInfoResponse),
+ (status = 401, description = "Unauthorized", body = ApiError),
+ (status = 404, description = "Task not found", body = ApiError),
+ (status = 503, description = "Database not configured or daemon not connected", body = ApiError),
+ (status = 500, description = "Internal server error", body = ApiError),
+ ),
+ security(
+ ("bearer_auth" = []),
+ ("api_key" = [])
+ ),
+ tag = "Mesh"
+)]
+pub async fn get_worktree_info(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path(id): Path<Uuid>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Get the task (scoped by owner)
+ let task = match repository::get_task_for_owner(pool, id, auth.owner_id).await {
+ Ok(Some(t)) => t,
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Task not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ tracing::error!("Failed to get task {}: {}", id, e);
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DB_ERROR", e.to_string())),
+ )
+ .into_response();
+ }
+ };
+
+ // Get daemon running the task
+ let Some(daemon_id) = task.daemon_id else {
+ // Task has no daemon, return empty worktree info
+ return Json(WorktreeInfoResponse {
+ task_id: id,
+ worktree_path: None,
+ exists: false,
+ stats: WorktreeStats {
+ files_changed: 0,
+ insertions: 0,
+ deletions: 0,
+ },
+ files: vec![],
+ branch: None,
+ head_sha: None,
+ })
+ .into_response();
+ };
+
+ // Send GetWorktreeInfo command to daemon
+ let command = DaemonCommand::GetWorktreeInfo { task_id: id };
+
+ if let Err(e) = state.send_daemon_command(daemon_id, command).await {
+ tracing::error!("Failed to send GetWorktreeInfo command: {}", e);
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DAEMON_ERROR", e)),
+ )
+ .into_response();
+ }
+
+ // Return placeholder - actual data will be streamed via WebSocket
+ // For now, return empty data indicating the request is being processed
+ Json(WorktreeInfoResponse {
+ task_id: id,
+ worktree_path: None,
+ exists: false,
+ stats: WorktreeStats {
+ files_changed: 0,
+ insertions: 0,
+ deletions: 0,
+ },
+ files: vec![],
+ branch: None,
+ head_sha: None,
+ })
+ .into_response()
+}
+
/// Request to check if a target directory exists.
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
@@ -2350,6 +2505,7 @@ pub async fn reassign_task(
patch_data,
patch_base_sha,
local_only,
+ supervisor_worktree_task_id: None, // Not spawned by supervisor
};
tracing::info!(
@@ -2688,6 +2844,7 @@ pub async fn continue_task(
patch_data: None,
patch_base_sha: None,
local_only,
+ supervisor_worktree_task_id: None, // Not spawned by supervisor
};
tracing::info!(
@@ -3612,6 +3769,7 @@ pub async fn branch_task(
patch_data,
patch_base_sha,
local_only: false, // No contract, so not local_only
+ supervisor_worktree_task_id: None, // Not spawned by supervisor
};
if let Err(e) = state.send_daemon_command(target_daemon_id, command).await {