From 786510379bed060db2b3742b7dfca671552d2c34 Mon Sep 17 00:00:00 2001 From: soryu Date: Mon, 19 Jan 2026 02:26:09 +0000 Subject: Make sure tasks can continue --- makima/src/daemon/task/manager.rs | 108 +++++++++++++++++++++++++++++++++----- makima/src/daemon/ws/protocol.rs | 9 ++++ 2 files changed, 105 insertions(+), 12 deletions(-) (limited to 'makima/src/daemon') diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index 029d026..80b7039 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -1171,6 +1171,8 @@ impl TaskManager { contract_id, is_supervisor, autonomous_loop, + resume_session, + conversation_history, } => { tracing::info!( task_id = %task_id, @@ -1183,6 +1185,7 @@ impl TaskManager { is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, autonomous_loop = autonomous_loop, + resume_session = resume_session, target_repo_path = ?target_repo_path, completion_action = ?completion_action, continue_from_task_id = ?continue_from_task_id, @@ -1195,7 +1198,8 @@ impl TaskManager { task_id, task_name, plan, repo_url, base_branch, target_branch, parent_task_id, depth, is_orchestrator, is_supervisor, target_repo_path, completion_action, continue_from_task_id, - copy_files, contract_id, autonomous_loop + copy_files, contract_id, autonomous_loop, resume_session, + conversation_history, ).await?; } DaemonCommand::PauseTask { task_id } => { @@ -1275,6 +1279,8 @@ impl TaskManager { None, // copy_files contract_id, false, // autonomous_loop - supervisors don't use this + false, // resume_session - respawning from scratch + None, // conversation_history - not needed for fresh respawn ).await { tracing::error!( task_id = %task_id, @@ -1490,6 +1496,8 @@ impl TaskManager { copy_files: Option>, contract_id: Option, autonomous_loop: bool, + resume_session: bool, + conversation_history: Option, ) -> TaskResult<()> { tracing::info!(task_id = %task_id, is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, depth = depth, "=== SPAWN_TASK START ==="); @@ -1566,7 +1574,8 @@ impl TaskManager { if let Err(e) = inner.run_task( task_id, task_name, plan, repo_url, base_branch, target_branch, is_orchestrator, is_supervisor, target_repo_path, completion_action, - continue_from_task_id, copy_files, contract_id, autonomous_loop + continue_from_task_id, copy_files, contract_id, autonomous_loop, resume_session, + conversation_history, ).await { tracing::error!(task_id = %task_id, error = %e, "Task execution failed"); inner.mark_failed(task_id, &e.to_string()).await; @@ -2909,11 +2918,39 @@ impl TaskManagerInner { copy_files: Option>, contract_id: Option, autonomous_loop: bool, + resume_session: bool, + conversation_history: Option, ) -> Result<(), DaemonError> { - tracing::info!(task_id = %task_id, is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, "=== RUN_TASK START ==="); + tracing::info!(task_id = %task_id, is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, resume_session = resume_session, "=== RUN_TASK START ==="); + + // If resuming session, try to find existing worktree first + let existing_worktree = if resume_session { + match self.find_worktree_for_task(task_id).await { + Ok(path) => { + tracing::info!(task_id = %task_id, path = %path.display(), "Found existing worktree for session resume"); + Some(path) + } + Err(e) => { + tracing::warn!(task_id = %task_id, error = %e, "No existing worktree found for resume, will create new"); + None + } + } + } else { + None + }; // Determine working directory - let working_dir = if let Some(ref source) = repo_source { + let has_existing_worktree = existing_worktree.is_some(); + let working_dir = if let Some(existing) = existing_worktree { + // Reuse existing worktree for session resume + let msg = DaemonMessage::task_output( + task_id, + format!("Resuming session in existing worktree: {}\n", existing.display()), + false, + ); + let _ = self.ws_tx.send(msg).await; + existing + } else if let Some(ref source) = repo_source { if is_new_repo_request(source) { // Explicit new repo request: new:// or new://project-name tracing::info!( @@ -3388,14 +3425,61 @@ impl TaskManagerInner { // Clone extra_env for use in autonomous loop iterations let extra_env_for_loop = extra_env.clone(); - tracing::debug!(task_id = %task_id, has_system_prompt = system_prompt.is_some(), "Calling process_manager.spawn()..."); - let mut process = self.process_manager - .spawn_with_system_prompt(&working_dir, &full_plan, extra_env, system_prompt.as_deref()) - .await - .map_err(|e| { - tracing::error!(task_id = %task_id, error = %e, "Failed to spawn Claude process"); - DaemonError::Task(TaskError::SetupFailed(e.to_string())) - })?; + tracing::debug!(task_id = %task_id, has_system_prompt = system_prompt.is_some(), resume_session = resume_session, "Calling process_manager.spawn()..."); + let mut process = if resume_session { + // Use --continue flag to resume from previous session + // Build continuation prompt based on whether worktree exists + let continuation_prompt = if has_existing_worktree { + // Worktree exists: Claude's session state should work + format!( + "Resuming previous session. Continue from where you left off.\n\n{}", + full_plan + ) + } else if let Some(ref history) = conversation_history { + // Worktree missing: inject conversation history as context + let history_str = serde_json::to_string_pretty(history).unwrap_or_default(); + format!( + "Resuming previous session. Here is the conversation history from the previous session:\n\n\ + \n{}\n\n\n\ + Continue from where you left off with this task:\n\n{}", + history_str, + full_plan + ) + } else { + // No history available: just the plan + format!("Resuming with plan:\n\n{}", full_plan) + }; + + let resume_msg = if has_existing_worktree { + "Using --continue to resume previous conversation...\n" + } else if conversation_history.is_some() { + "Worktree not found. Resuming with injected conversation history...\n" + } else { + "Resuming without conversation history (worktree not found)...\n" + }; + let msg = DaemonMessage::task_output( + task_id, + resume_msg.to_string(), + false, + ); + let _ = self.ws_tx.send(msg).await; + + self.process_manager + .spawn_continue(&working_dir, &continuation_prompt, extra_env, system_prompt.as_deref()) + .await + .map_err(|e| { + tracing::error!(task_id = %task_id, error = %e, "Failed to spawn Claude process with --continue"); + DaemonError::Task(TaskError::SetupFailed(e.to_string())) + })? + } else { + self.process_manager + .spawn_with_system_prompt(&working_dir, &full_plan, extra_env, system_prompt.as_deref()) + .await + .map_err(|e| { + tracing::error!(task_id = %task_id, error = %e, "Failed to spawn Claude process"); + DaemonError::Task(TaskError::SetupFailed(e.to_string())) + })? + }; // Register the process PID for graceful shutdown tracking if let Some(pid) = process.id() { diff --git a/makima/src/daemon/ws/protocol.rs b/makima/src/daemon/ws/protocol.rs index 5c9004b..d0bcc19 100644 --- a/makima/src/daemon/ws/protocol.rs +++ b/makima/src/daemon/ws/protocol.rs @@ -378,6 +378,15 @@ pub enum DaemonCommand { /// without a COMPLETION_GATE indicating ready: true. #[serde(rename = "autonomousLoop", default)] autonomous_loop: bool, + /// Whether to resume from a previous session using --continue flag. + /// When enabled, the daemon will reuse the existing worktree and call + /// Claude with --continue to maintain conversation history. + #[serde(rename = "resumeSession", default)] + resume_session: bool, + /// Conversation history for fallback when worktree doesn't exist. + /// Used to inject previous conversation context into the prompt. + #[serde(rename = "conversationHistory", default)] + conversation_history: Option, }, /// Pause a running task. -- cgit v1.2.3