summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-13 21:39:29 +0000
committersoryu <soryu@soryu.co>2026-02-13 21:39:29 +0000
commitfe6de783c16101392612d8c3719c8d12d9a28916 (patch)
treed6938cf4a174a62a55dc36cb85102af6b2aa129e
parent5edaf1228b4e48a441b98c49f58de312b7924ed6 (diff)
downloadsoryu-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.rs161
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,