fix: unblock sessions stuck in PAUSED state after approval resolved
Sessions that had an approval resolved (or never needed one) but never received OrchestrationResumedEvent would show the last tool as RUNNING forever with no subsequent events. Three coordinated fixes: 1. SessionOrchestrator now emits OrchestrationResumedEvent in both the approved and rejected approval branches, so the paused flag clears correctly going forward. 2. SessionEventBridge.replaySnapshot() detects the stuck pattern (pendingApproval=true with no unresolved requests) and appends a synthetic OrchestrationResumedEvent so historical sessions self-heal on next TUI connect. 3. DomainEventMapper maps OrchestrationResumedEvent → SessionResumed; TUI SessionsReducer/SnapshotPhaseReducer handle the new message, clearing pendingApproval and setting status back to ACTIVE. Also adds server-restart recovery path in DefaultSessionOrchestrator (emit decision + resume events directly when no live deferred exists) and pre-registers pending approvals in ServerModule.start() to close the race between reconnect and ApprovalResponse routing.
This commit is contained in:
@@ -165,6 +165,7 @@ object SessionsReducer {
|
||||
val (result, effects) = when (msg) {
|
||||
is ServerMessage.SessionStarted -> processSessionStartedMessage(msg, clock, sessions)
|
||||
is ServerMessage.SessionPaused -> processSessionPausedMessage(msg, sessions, clock)
|
||||
is ServerMessage.SessionResumed -> processSessionResumedMessage(msg, sessions, clock)
|
||||
is ServerMessage.SessionCompleted -> processSessionCompletedMessage(sessions, msg, clock)
|
||||
is ServerMessage.SessionFailed -> processSessionFailedMessage(sessions, msg, clock)
|
||||
is ServerMessage.StageStarted -> processStageStartedMessage(msg, sessions)
|
||||
@@ -189,6 +190,7 @@ object SessionsReducer {
|
||||
private fun sessionIdFromMessage(msg: ServerMessage): String? = when (msg) {
|
||||
is ServerMessage.SessionStarted -> msg.sessionId.value
|
||||
is ServerMessage.SessionPaused -> msg.sessionId.value
|
||||
is ServerMessage.SessionResumed -> msg.sessionId.value
|
||||
is ServerMessage.SessionCompleted -> msg.sessionId.value
|
||||
is ServerMessage.SessionFailed -> msg.sessionId.value
|
||||
is ServerMessage.StageStarted -> msg.sessionId.value
|
||||
@@ -507,6 +509,20 @@ object SessionsReducer {
|
||||
) to emptyList()
|
||||
}
|
||||
|
||||
private fun processSessionResumedMessage(
|
||||
msg: ServerMessage.SessionResumed,
|
||||
sessions: SessionsState,
|
||||
clock: () -> Long,
|
||||
): Pair<SessionsState, List<Effect>> = sessions.copy(
|
||||
sessions = sessions.sessions.map { s ->
|
||||
if (s.id == msg.sessionId.value) s.copy(
|
||||
status = "ACTIVE",
|
||||
pendingApproval = null,
|
||||
lastEventAt = clock(),
|
||||
) else s
|
||||
},
|
||||
) to emptyList()
|
||||
|
||||
private fun processSessionStartedMessage(
|
||||
msg: ServerMessage.SessionStarted,
|
||||
clock: () -> Long,
|
||||
|
||||
@@ -64,6 +64,7 @@ object SnapshotPhaseReducer {
|
||||
internal fun sessionIdOf(msg: ServerMessage): String? = when (msg) {
|
||||
is ServerMessage.SessionStarted -> msg.sessionId.value
|
||||
is ServerMessage.SessionPaused -> msg.sessionId.value
|
||||
is ServerMessage.SessionResumed -> msg.sessionId.value
|
||||
is ServerMessage.SessionCompleted -> msg.sessionId.value
|
||||
is ServerMessage.SessionFailed -> msg.sessionId.value
|
||||
is ServerMessage.StageStarted -> msg.sessionId.value
|
||||
|
||||
Reference in New Issue
Block a user