diff options
Diffstat (limited to 'makima/src/server/handlers/directives.rs')
| -rw-r--r-- | makima/src/server/handlers/directives.rs | 85 |
1 files changed, 68 insertions, 17 deletions
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs index d1edf7e..01c4659 100644 --- a/makima/src/server/handlers/directives.rs +++ b/makima/src/server/handlers/directives.rs @@ -18,7 +18,10 @@ use crate::db::models::{ OrderListResponse, }; use crate::db::repository; -use crate::orchestration::directive::{build_cleanup_prompt, build_order_pickup_prompt}; +use crate::orchestration::directive::{ + build_cleanup_prompt, build_order_pickup_prompt, classify_goal_change, + try_interrupt_planner_with_goal_edit, GoalChangeKind, GoalEditInterruptResult, +}; use crate::server::auth::Authenticated; use crate::server::messages::ApiError; use crate::server::state::SharedState; @@ -827,31 +830,79 @@ pub async fn update_goal( .into_response(); }; - // Save old goal to history before overwriting (best-effort) - match repository::get_directive_for_owner(pool, auth.owner_id, id).await { - Ok(Some(current)) => { - if let Err(e) = repository::save_directive_goal_history(pool, id, ¤t.goal).await - { - tracing::warn!( - directive_id = %id, - error = %e, - "Failed to save goal history before update — continuing with goal update" - ); - } - } - Ok(None) => { - // Directive not found — update_directive_goal will handle this - } + // Fetch the current directive so we can: + // 1. Save the old goal to history (best-effort). + // 2. Decide whether to fire a goal-edit interrupt at a running planner. + let current = match repository::get_directive_for_owner(pool, auth.owner_id, id).await { + Ok(Some(d)) => Some(d), + Ok(None) => None, Err(e) => { tracing::warn!( directive_id = %id, error = %e, "Failed to fetch current directive for goal history — continuing with goal update" ); + None + } + }; + + // Save old goal to history before overwriting (best-effort). + if let Some(ref current) = current { + if let Err(e) = repository::save_directive_goal_history(pool, id, ¤t.goal).await { + tracing::warn!( + directive_id = %id, + error = %e, + "Failed to save goal history before update — continuing with goal update" + ); + } + } + + // Goal-edit interrupt cycle: if a planner task is currently running for + // this directive AND the goal change classifies as 'small', interrupt the + // running planner via SendMessage instead of clearing it (which would + // trigger a fresh replan on the next orchestrator tick). + let mut interrupted = false; + if let Some(ref current) = current { + if current.orchestrator_task_id.is_some() + && classify_goal_change(¤t.goal, &req.goal) == GoalChangeKind::Small + { + match try_interrupt_planner_with_goal_edit( + pool, + &state, + id, + ¤t.goal, + &req.goal, + ) + .await + { + Ok(GoalEditInterruptResult::Sent) => { + interrupted = true; + } + Ok(GoalEditInterruptResult::Skipped) => {} + Err(e) => { + tracing::warn!( + directive_id = %id, + error = %e, + "Goal-edit interrupt attempt errored — falling back to replan" + ); + } + } } } - match repository::update_directive_goal(pool, auth.owner_id, id, &req.goal).await { + // If we successfully interrupted a running planner, persist the new goal + // WITHOUT clearing the orchestrator task — the planner will react to the + // SendMessage and adjust in-flight. Otherwise, fall through to the normal + // path which clears orchestrator_task_id and lets phase_replanning kick + // in on the next tick. + let update_result = if interrupted { + repository::update_directive_goal_keep_orchestrator(pool, auth.owner_id, id, &req.goal) + .await + } else { + repository::update_directive_goal(pool, auth.owner_id, id, &req.goal).await + }; + + match update_result { Ok(Some(directive)) => Json(directive).into_response(), Ok(None) => ( StatusCode::NOT_FOUND, |
