summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-14 02:22:42 +0000
committersoryu <soryu@soryu.co>2026-02-14 02:22:42 +0000
commitba1a4790aa576fc4bfae5191336cd81a9effd785 (patch)
treea43c15f082fedc238c9f622a3c380924de1ee417
parentc1e55ce4fec79f9909b957f86bd7fa8b76939746 (diff)
downloadsoryu-ba1a4790aa576fc4bfae5191336cd81a9effd785.tar.gz
soryu-ba1a4790aa576fc4bfae5191336cd81a9effd785.zip
WIP: heartbeat checkpoint
-rw-r--r--makima/frontend/src/routes/mesh.tsx2
-rw-r--r--makima/src/daemon/task/manager.rs17
-rw-r--r--makima/src/server/handlers/mesh.rs10
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
),
)),