feat(kernel,server): rehydrate() + POST /sessions/{id}/resume

This commit is contained in:
2026-06-08 10:13:33 +04:00
parent 5ad945ccb4
commit b830528d31
5 changed files with 192 additions and 1 deletions
@@ -188,6 +188,25 @@ class ServerModule(
}
}
/**
* Rehydrates [artifactContentCache] from durable events and then resumes the session.
* Called by [POST /sessions/{id}/resume] to restart a session after a server restart
* without re-running already-completed stages.
*/
fun launchSessionResumeWithRehydrate(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
activeSessionJobs.computeIfAbsent(sessionId) {
moduleScope.launch {
runCatching {
orchestrator.rehydrate(sessionId)
orchestrator.resume(sessionId, graph, defaultOrchestrationConfig)
}.onFailure { e ->
log.error("resumeSession (rehydrate): session={} failed", sessionId.value, e)
}
activeSessionJobs.remove(sessionId)
}
}
}
private fun resumeAbandonedSessions() {
eventStore.allSessionIds().forEach { sessionId ->
val orchState = orchestrationRepository.getState(sessionId)
@@ -43,6 +43,7 @@ fun Route.sessionRoutes(module: ServerModule) {
getSessionRoute(module)
cancelSessionRoute(module)
undoSessionRoute(module)
resumeSessionRoute(module)
getEventsRoute(module)
webSocket("/stream") {
val id = call.parameters["id"] ?: return@webSocket
@@ -98,6 +99,21 @@ private fun Route.undoSessionRoute(module: ServerModule) {
}
}
private fun Route.resumeSessionRoute(module: ServerModule) {
post("/resume") {
val id = call.parameters["id"]
?: return@post call.respond(HttpStatusCode.BadRequest, "Missing session id")
val orchState = runCatching { module.orchestrationRepository.getState(TypeId(id)) }.getOrNull()
?: return@post call.respond(HttpStatusCode.NotFound, "Session not found")
val workflowId = orchState.workflowId.takeIf { it.isNotBlank() }
?: return@post call.respond(HttpStatusCode.BadRequest, "Session has no workflowId")
val graph = module.workflowRegistry.find(workflowId)
?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: $workflowId")
module.launchSessionResumeWithRehydrate(TypeId(id), graph)
call.respond(HttpStatusCode.Accepted)
}
}
private fun Route.getEventsRoute(module: ServerModule) {
get("/events") {
val id = call.parameters["id"]