diff options
| author | soryu <soryu@soryu.co> | 2026-02-14 02:22:42 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-14 02:22:42 +0000 |
| commit | ba1a4790aa576fc4bfae5191336cd81a9effd785 (patch) | |
| tree | a43c15f082fedc238c9f622a3c380924de1ee417 | |
| parent | c1e55ce4fec79f9909b957f86bd7fa8b76939746 (diff) | |
| download | soryu-ba1a4790aa576fc4bfae5191336cd81a9effd785.tar.gz soryu-ba1a4790aa576fc4bfae5191336cd81a9effd785.zip | |
WIP: heartbeat checkpoint
| -rw-r--r-- | makima/frontend/src/routes/mesh.tsx | 2 | ||||
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 17 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh.rs | 10 |
3 files changed, 19 insertions, 10 deletions
diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx index cb4a77c..1d1db84 100644 --- a/makima/frontend/src/routes/mesh.tsx +++ b/makima/frontend/src/routes/mesh.tsx @@ -852,7 +852,7 @@ export default function MeshPage() { <div className="flex-1 min-h-0 overflow-hidden"> <TaskOutput entries={taskOutputEntries} - isStreaming={isStreaming || taskDetail.status === "running"} + isStreaming={isStreaming || taskDetail.status === "running" || taskDetail.status === "starting"} viewingSubtaskName={viewingSubtaskName} onClearSubtaskView={viewingSubtaskId ? () => { setViewingSubtaskId(null); diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index ce5a580..4ed2ba1 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -5192,12 +5192,19 @@ impl TaskManagerInner { // Check if this is a "result" message indicating task completion // With --input-format=stream-json, Claude waits for more input after completion - // We close stdin to signal EOF and let the process exit if line.json_type.as_deref() == Some("result") { - tracing::info!(task_id = %task_id, "Received result message, closing stdin to signal completion"); - let mut stdin_guard = stdin_handle_for_completion.lock().await; - if let Some(mut stdin) = stdin_guard.take() { - let _ = stdin.shutdown().await; + if autonomous_loop { + // In autonomous loop mode, close stdin to let the process exit + // so we can spawn the next iteration with --continue + tracing::info!(task_id = %task_id, "Received result message in autonomous loop, closing stdin to signal completion"); + let mut stdin_guard = stdin_handle_for_completion.lock().await; + if let Some(mut stdin) = stdin_guard.take() { + let _ = stdin.shutdown().await; + } + } else { + // In interactive mode, keep stdin open so the user can send + // follow-up messages. Claude will stay alive waiting for input. + tracing::info!(task_id = %task_id, "Received result message, keeping stdin open for interactive input"); } } diff --git a/makima/src/server/handlers/mesh.rs b/makima/src/server/handlers/mesh.rs index eb87e17..c840676 100644 --- a/makima/src/server/handlers/mesh.rs +++ b/makima/src/server/handlers/mesh.rs @@ -1070,17 +1070,19 @@ pub async fn send_message( } }; - // Check if task is running (except for AUTH_CODE messages and supervisor tasks) - // Supervisor tasks can receive messages even when not running - daemon will respawn Claude + // Check if task is in a state that can receive messages + // Allow "running" and "starting" (to handle race between status update and message send) + // Also allow AUTH_CODE messages and supervisor tasks regardless of status let is_auth_code = req.message.starts_with("AUTH_CODE:"); let is_supervisor = task.is_supervisor; - if task.status != "running" && !is_auth_code && !is_supervisor { + let can_receive_message = task.status == "running" || task.status == "starting"; + if !can_receive_message && !is_auth_code && !is_supervisor { return ( StatusCode::BAD_REQUEST, Json(ApiError::new( "INVALID_STATE", format!( - "Cannot send message to task in status: {}. Task must be running.", + "Cannot send message to task in status: {}. Task must be running or starting.", task.status ), )), |
