fix: PAUSED resume after server restart, diff viewer only shows last tool
This commit is contained in:
@@ -9,7 +9,9 @@ import com.correx.core.approvals.DefaultApprovalRepository
|
||||
import com.correx.core.artifactstore.ArtifactStore
|
||||
import com.correx.core.config.ApprovalConfig
|
||||
import com.correx.core.events.events.ApprovalRequestedEvent
|
||||
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||
import com.correx.core.events.stores.EventStore
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
|
||||
import com.correx.core.kernel.orchestration.OrchestrationConfig
|
||||
import com.correx.core.kernel.orchestration.OrchestrationRepository
|
||||
@@ -17,6 +19,7 @@ import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.sessions.DefaultSessionRepository
|
||||
import com.correx.core.tools.registry.ToolRegistry
|
||||
import com.correx.core.events.orchestration.OrchestrationStatus
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -55,6 +58,7 @@ class ServerModule(
|
||||
)
|
||||
|
||||
private var subscriptionJob: Job? = null
|
||||
private val activeSessionJobs = ConcurrentHashMap<SessionId, Job>()
|
||||
|
||||
/**
|
||||
* Subscribe to the event store and forward [ApprovalRequestedEvent] payloads to
|
||||
@@ -73,26 +77,50 @@ class ServerModule(
|
||||
.filter { it.payload is ApprovalRequestedEvent }
|
||||
.onEach { approvalCoordinator.onApprovalRequested(it.payload as ApprovalRequestedEvent) }
|
||||
.launchIn(moduleScope)
|
||||
|
||||
// When a PAUSED session's approval is resolved without a live orchestrator coroutine
|
||||
// (server was restarted while approval was pending), emit OrchestrationResumedEvent
|
||||
// triggers this subscription to continue the session.
|
||||
eventStore.subscribeAll()
|
||||
.filter { it.payload is OrchestrationResumedEvent }
|
||||
.onEach { event ->
|
||||
val sessionId = (event.payload as OrchestrationResumedEvent).sessionId
|
||||
if (activeSessionJobs.containsKey(sessionId)) return@onEach
|
||||
val orchState = orchestrationRepository.getState(sessionId)
|
||||
if (orchState.status != OrchestrationStatus.RUNNING) return@onEach
|
||||
val graph = workflowRegistry.find(orchState.workflowId) ?: return@onEach
|
||||
launchSessionResume(sessionId, graph)
|
||||
}
|
||||
.launchIn(moduleScope)
|
||||
}
|
||||
|
||||
private fun launchSessionResume(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
|
||||
if (activeSessionJobs.containsKey(sessionId)) return
|
||||
val job = moduleScope.launch {
|
||||
runCatching {
|
||||
orchestrator.resume(sessionId, graph, defaultOrchestrationConfig)
|
||||
}.onFailure { e ->
|
||||
log.error("resumeSession: session={} failed", sessionId.value, e)
|
||||
}
|
||||
activeSessionJobs.remove(sessionId)
|
||||
}
|
||||
activeSessionJobs[sessionId] = job
|
||||
}
|
||||
|
||||
private fun resumeAbandonedSessions() {
|
||||
eventStore.allSessionIds().forEach { sessionId ->
|
||||
val orchState = orchestrationRepository.getState(sessionId)
|
||||
if (orchState.status != OrchestrationStatus.RUNNING &&
|
||||
orchState.status != OrchestrationStatus.PAUSED
|
||||
) return@forEach
|
||||
// Skip PAUSED sessions — they are waiting for user approval, not for computation.
|
||||
// preRegisterPendingApprovals() already routes their responses. When the user
|
||||
// approves, submitApprovalDecision emits OrchestrationResumedEvent which triggers
|
||||
// the subscription above to continue the session.
|
||||
if (orchState.status != OrchestrationStatus.RUNNING) return@forEach
|
||||
val graph = workflowRegistry.find(orchState.workflowId) ?: run {
|
||||
log.warn("resumeAbandoned: no graph for session={} workflowId={}", sessionId.value, orchState.workflowId)
|
||||
return@forEach
|
||||
}
|
||||
log.info("resumeAbandoned: launching session={} workflow={}", sessionId.value, orchState.workflowId)
|
||||
moduleScope.launch {
|
||||
runCatching {
|
||||
orchestrator.resume(sessionId, graph, defaultOrchestrationConfig)
|
||||
}.onFailure { e ->
|
||||
log.error("resumeAbandoned: session={} failed", sessionId.value, e)
|
||||
}
|
||||
}
|
||||
launchSessionResume(sessionId, graph)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user