summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/handlers')
-rw-r--r--makima/src/server/handlers/directives.rs50
1 files changed, 40 insertions, 10 deletions
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs
index 01c4659..44bf4ac 100644
--- a/makima/src/server/handlers/directives.rs
+++ b/makima/src/server/handlers/directives.rs
@@ -20,7 +20,8 @@ use crate::db::models::{
use crate::db::repository;
use crate::orchestration::directive::{
build_cleanup_prompt, build_order_pickup_prompt, classify_goal_change,
- try_interrupt_planner_with_goal_edit, GoalChangeKind, GoalEditInterruptResult,
+ try_cancel_running_planner, try_interrupt_planner_with_goal_edit,
+ GoalChangeKind, GoalEditInterruptResult,
};
use crate::server::auth::Authenticated;
use crate::server::messages::ApiError;
@@ -895,6 +896,25 @@ pub async fn update_goal(
// 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.
+ //
+ // CRITICAL: when going down the "clear" path, we must also CANCEL the
+ // running planner. Otherwise the orphaned task keeps producing add-step
+ // calls based on the old goal, racing the freshly-spawned replanner.
+ if !interrupted {
+ if let Some(ref current) = current {
+ if let Some(orch_task_id) = current.orchestrator_task_id {
+ if let Err(e) = try_cancel_running_planner(pool, &state, id, orch_task_id).await {
+ tracing::warn!(
+ directive_id = %id,
+ task_id = %orch_task_id,
+ error = %e,
+ "Failed to cancel orphaned planner — proceeding with clear anyway"
+ );
+ }
+ }
+ }
+ }
+
let update_result = if interrupted {
repository::update_directive_goal_keep_orchestrator(pool, auth.owner_id, id, &req.goal)
.await
@@ -902,22 +922,32 @@ pub async fn update_goal(
repository::update_directive_goal(pool, auth.owner_id, id, &req.goal).await
};
- match update_result {
+ let response = match update_result {
Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
Err(e) => {
tracing::error!("Failed to update goal: {}", e);
- (
+ return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ApiError::new("UPDATE_FAILED", &e.to_string())),
)
- .into_response()
+ .into_response();
}
- }
+ };
+
+ // Nudge the directive reconciler so the user does not wait up to 15s for
+ // the next interval tick before the new planner is spawned (clear path) or
+ // the small-edit interrupt is consumed (keep path). Best-effort: if the
+ // channel is full or closed we just rely on the normal interval.
+ state.kick_directive_reconciler();
+
+ response
}
// =============================================================================