diff options
| author | soryu <soryu@soryu.co> | 2026-02-13 21:39:29 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-13 21:39:29 +0000 |
| commit | fe6de783c16101392612d8c3719c8d12d9a28916 (patch) | |
| tree | d6938cf4a174a62a55dc36cb85102af6b2aa129e | |
| parent | 5edaf1228b4e48a441b98c49f58de312b7924ed6 (diff) | |
| download | soryu-makima/makima-system--fix-task-claude-instance-not-receiv-5549bf3e.tar.gz soryu-makima/makima-system--fix-task-claude-instance-not-receiv-5549bf3e.zip | |
feat: makima system: Fix task Claude instance not receiving user inputsmakima/makima-system--fix-task-claude-instance-not-receiv-5549bf3e
| -rw-r--r-- | makima/src/server/handlers/mesh_chat.rs | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/makima/src/server/handlers/mesh_chat.rs b/makima/src/server/handlers/mesh_chat.rs index 638a4d3..e2a9f78 100644 --- a/makima/src/server/handlers/mesh_chat.rs +++ b/makima/src/server/handlers/mesh_chat.rs @@ -118,6 +118,16 @@ pub async fn mesh_toplevel_chat_handler( .into_response(); }; + // BYPASS: If context_type is "task" or "subtask" and context_task_id is set, + // send the message directly to the task daemon without going through the LLM. + // This ensures the user's message reliably reaches the Claude Code process. + let context_type = request.context_type.as_deref().unwrap_or("mesh"); + if context_type == "task" || context_type == "subtask" { + if let Some(task_id) = request.context_task_id { + return send_message_directly_to_task(pool, &state, auth.owner_id, task_id, &request.message, context_type).await; + } + } + // Parse model selection (default to Claude Sonnet) let model = request .model @@ -275,6 +285,15 @@ pub async fn mesh_chat_handler( .into_response(); }; + // BYPASS: If context_type is "task" or "subtask", send the message directly + // to the task daemon without going through the LLM agentic loop. + // Use context_task_id if provided, otherwise fall back to the path task_id. + let context_type = request.context_type.as_deref().unwrap_or("mesh"); + if context_type == "task" || context_type == "subtask" { + let target_task_id = request.context_task_id.unwrap_or(task_id); + return send_message_directly_to_task(pool, &state, auth.owner_id, target_task_id, &request.message, context_type).await; + } + // Get the context task (scoped by owner) let task = match repository::get_task_for_owner(pool, task_id, auth.owner_id).await { Ok(Some(task)) => task, @@ -530,6 +549,148 @@ async fn build_mesh_overview_context(pool: &sqlx::PgPool, state: &SharedState, o context } +/// Send a message directly to a task's Claude Code process via the daemon, +/// bypassing the LLM agentic loop entirely. This ensures user messages reliably +/// reach the task when the user is viewing a specific task context. +async fn send_message_directly_to_task( + pool: &sqlx::PgPool, + state: &SharedState, + owner_id: Uuid, + task_id: Uuid, + message: &str, + context_type: &str, +) -> axum::response::Response { + // Look up the task in the DB + let task = match repository::get_task_for_owner(pool, task_id, owner_id).await { + Ok(Some(t)) => t, + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(MeshChatResponse { + response: format!("Task {} not found", task_id), + tool_calls: vec![], + pending_questions: None, + }), + ) + .into_response(); + } + Err(e) => { + tracing::error!("Failed to get task {}: {}", task_id, e); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(MeshChatResponse { + response: format!("Database error: {}", e), + tool_calls: vec![], + pending_questions: None, + }), + ) + .into_response(); + } + }; + + // Get the daemon_id from the task record + let daemon_id = match task.daemon_id { + Some(id) => id, + None => { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(MeshChatResponse { + response: "Task has no assigned daemon. Cannot send message.".to_string(), + tool_calls: vec![], + pending_questions: None, + }), + ) + .into_response(); + } + }; + + // Check if daemon is connected + if !state.is_daemon_connected(daemon_id) { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(MeshChatResponse { + response: "The daemon running this task is disconnected. Cannot send message.".to_string(), + tool_calls: vec![], + pending_questions: None, + }), + ) + .into_response(); + } + + // Send the message directly to the task via daemon command + let command = DaemonCommand::SendMessage { + task_id, + message: message.to_string(), + }; + + if let Err(e) = state.send_daemon_command(daemon_id, command).await { + tracing::error!("Failed to send message to task {} via daemon {}: {}", task_id, daemon_id, e); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(MeshChatResponse { + response: format!("Failed to send message to task: {}", e), + tool_calls: vec![], + pending_questions: None, + }), + ) + .into_response(); + } + + tracing::info!( + task_id = %task_id, + daemon_id = %daemon_id, + message_len = message.len(), + context_type = %context_type, + "Direct message sent to task (LLM bypass)" + ); + + // Save the user message to chat history for conversation record + if let Ok(conversation) = repository::get_or_create_active_conversation(pool, owner_id).await { + if let Err(e) = repository::add_chat_message( + pool, + conversation.id, + "user", + message, + context_type, + Some(task_id), + None, + None, + ) + .await + { + tracing::warn!("Failed to save user message to DB: {}", e); + } + + // Save a system note as assistant message so the chat log is complete + let response_text = format!("Message sent to task {}", task.name); + if let Err(e) = repository::add_chat_message( + pool, + conversation.id, + "assistant", + &response_text, + context_type, + Some(task_id), + None, + None, + ) + .await + { + tracing::warn!("Failed to save assistant message to DB: {}", e); + } + } + + // Return success response in the same format as mesh chat + ( + StatusCode::OK, + Json(MeshChatResponse { + response: "Message sent to task".to_string(), + tool_calls: vec![], + pending_questions: None, + }), + ) + .into_response() +} + /// Run the shared agentic loop for mesh chat async fn run_mesh_agentic_loop( pool: &sqlx::PgPool, |
