refactor(server): single guarded freestyleHandoff shared by run and resume launchers
launchSessionRun and launchSessionResumeWithRehydrate each inlined their own copy of the freestyle phase-2 handoff with different guard sets — the run path had none, so a failed planning run still called lockAndRun and emitted a spurious ExecutionPlanRejected. Both now call one handoff that checks graph id, planning result, and an existing plan lock before locking and running phase 2.
This commit is contained in:
@@ -225,11 +225,8 @@ class ServerModule(
|
|||||||
}
|
}
|
||||||
bindProjectProfile(sessionId)
|
bindProjectProfile(sessionId)
|
||||||
runCatching {
|
runCatching {
|
||||||
orchestrator.run(sessionId, graph, sessionConfig)
|
val result = orchestrator.run(sessionId, graph, sessionConfig)
|
||||||
// After a successful freestyle planning run, lock the plan and execute phase 2.
|
freestyleHandoff(sessionId, graph, result)
|
||||||
if (graph.id == "freestyle_planning") {
|
|
||||||
freestyleDriver?.lockAndRun(sessionId)
|
|
||||||
}
|
|
||||||
// Distil this run's decisions into durable project memory on completion.
|
// Distil this run's decisions into durable project memory on completion.
|
||||||
projectMemory?.let { pm -> pm.persist(sessionId, pm.repoRoot()) }
|
projectMemory?.let { pm -> pm.persist(sessionId, pm.repoRoot()) }
|
||||||
// Propose learned profile adaptations based on session journal (opt-in, never auto-applied).
|
// Propose learned profile adaptations based on session journal (opt-in, never auto-applied).
|
||||||
@@ -309,6 +306,25 @@ class ServerModule(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase-2 handoff for freestyle sessions, shared by every launcher that can finish a
|
||||||
|
* planning graph (fresh run and all resume paths). Locks the plan and executes it,
|
||||||
|
* guarded so a failed planning run never emits a spurious ExecutionPlanRejected and
|
||||||
|
* a session whose plan is already locked never double-locks.
|
||||||
|
*/
|
||||||
|
private suspend fun freestyleHandoff(
|
||||||
|
sessionId: SessionId,
|
||||||
|
graph: com.correx.core.transitions.graph.WorkflowGraph,
|
||||||
|
result: WorkflowResult,
|
||||||
|
) {
|
||||||
|
if (graph.id != "freestyle_planning") return
|
||||||
|
if (result !is WorkflowResult.Completed) return
|
||||||
|
val planAlreadyLocked = eventStore.read(sessionId)
|
||||||
|
.any { it.payload is ExecutionPlanLockedEvent }
|
||||||
|
if (planAlreadyLocked) return
|
||||||
|
freestyleDriver?.lockAndRun(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rehydrates [artifactContentCache] from durable events and then resumes the session.
|
* Rehydrates [artifactContentCache] from durable events and then resumes the session.
|
||||||
* Used by [POST /sessions/{id}/resume], boot-time abandoned-session pickup, and the
|
* Used by [POST /sessions/{id}/resume], boot-time abandoned-session pickup, and the
|
||||||
@@ -322,17 +338,7 @@ class ServerModule(
|
|||||||
runCatching {
|
runCatching {
|
||||||
orchestrator.rehydrate(sessionId)
|
orchestrator.rehydrate(sessionId)
|
||||||
val result = orchestrator.resume(sessionId, graph, orchestrationConfig())
|
val result = orchestrator.resume(sessionId, graph, orchestrationConfig())
|
||||||
// Freestyle handoff parity with launchSessionRun: a session killed between
|
freestyleHandoff(sessionId, graph, result)
|
||||||
// planning completion and plan lock would otherwise strand forever — the
|
|
||||||
// resume completes the planning graph but nothing ever locks the plan.
|
|
||||||
val planAlreadyLocked = eventStore.read(sessionId)
|
|
||||||
.any { it.payload is ExecutionPlanLockedEvent }
|
|
||||||
if (graph.id == "freestyle_planning" &&
|
|
||||||
result is WorkflowResult.Completed &&
|
|
||||||
!planAlreadyLocked
|
|
||||||
) {
|
|
||||||
freestyleDriver?.lockAndRun(sessionId)
|
|
||||||
}
|
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
log.error("resumeSession (rehydrate): session={} failed", sessionId.value, e)
|
log.error("resumeSession (rehydrate): session={} failed", sessionId.value, e)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user