fix: resolve four findings from code review
- Stop silently booting user from session view on completion (SessionsReducer.processSessionCompletedMessage no longer deselects) - Wrap StageToolManifest send in runCatching to survive WS disconnect after orchestrator launch in module scope - Fix navigateUp dead state: wrap to last workflow instead of -1 - Consolidate event store reads in SessionEventBridge (single read for both recentEvents and approval state), removing DefaultApprovalRepository dependency
This commit is contained in:
@@ -8,7 +8,9 @@ import com.correx.apps.server.protocol.SessionStateDto
|
|||||||
import com.correx.apps.server.protocol.StageToolDecl
|
import com.correx.apps.server.protocol.StageToolDecl
|
||||||
import com.correx.apps.server.protocol.ToolDecl
|
import com.correx.apps.server.protocol.ToolDecl
|
||||||
import com.correx.apps.server.registry.WorkflowRegistry
|
import com.correx.apps.server.registry.WorkflowRegistry
|
||||||
import com.correx.core.approvals.DefaultApprovalRepository
|
import com.correx.core.approvals.ApprovalProjector
|
||||||
|
import com.correx.core.approvals.DefaultApprovalReducer
|
||||||
|
import com.correx.core.approvals.model.ApprovalState
|
||||||
import com.correx.core.artifactstore.ArtifactStore
|
import com.correx.core.artifactstore.ArtifactStore
|
||||||
import com.correx.core.events.events.InferenceCompletedEvent
|
import com.correx.core.events.events.InferenceCompletedEvent
|
||||||
import com.correx.core.events.events.InferenceStartedEvent
|
import com.correx.core.events.events.InferenceStartedEvent
|
||||||
@@ -35,12 +37,13 @@ class SessionEventBridge(
|
|||||||
private val eventStore: EventStore,
|
private val eventStore: EventStore,
|
||||||
private val artifactStore: ArtifactStore,
|
private val artifactStore: ArtifactStore,
|
||||||
private val orchestrationRepository: OrchestrationRepository,
|
private val orchestrationRepository: OrchestrationRepository,
|
||||||
private val approvalRepository: DefaultApprovalRepository,
|
|
||||||
private val workflowRegistry: WorkflowRegistry,
|
private val workflowRegistry: WorkflowRegistry,
|
||||||
private val toolRegistry: ToolRegistry,
|
private val toolRegistry: ToolRegistry,
|
||||||
private val approvalCoordinator: ApprovalCoordinator? = null,
|
private val approvalCoordinator: ApprovalCoordinator? = null,
|
||||||
private val send: suspend (ServerMessage) -> Unit,
|
private val send: suspend (ServerMessage) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
private val approvalProjector = ApprovalProjector(DefaultApprovalReducer())
|
||||||
|
|
||||||
suspend fun replaySnapshot() {
|
suspend fun replaySnapshot() {
|
||||||
val lastGlobal = eventStore.lastGlobalSequence()
|
val lastGlobal = eventStore.lastGlobalSequence()
|
||||||
eventStore.allSessionIds().forEach { sessionId ->
|
eventStore.allSessionIds().forEach { sessionId ->
|
||||||
@@ -49,8 +52,14 @@ class SessionEventBridge(
|
|||||||
|
|
||||||
val lastSession = eventStore.lastSequence(sessionId) ?: 0L
|
val lastSession = eventStore.lastSequence(sessionId) ?: 0L
|
||||||
|
|
||||||
val approvalState = if (orchState.pendingApproval) {
|
// Single read from the event store — avoids inconsistency between
|
||||||
approvalRepository.getApprovalState(sessionId)
|
// approval state and recentEvents with a cursor-based store.
|
||||||
|
val events = eventStore.read(sessionId)
|
||||||
|
|
||||||
|
val approvalState: ApprovalState? = if (orchState.pendingApproval) {
|
||||||
|
events.fold(approvalProjector.initial()) { state: ApprovalState, event: StoredEvent ->
|
||||||
|
approvalProjector.apply(state, event)
|
||||||
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
val pendingApprovals = approvalState?.requests?.values
|
val pendingApprovals = approvalState?.requests?.values
|
||||||
@@ -66,7 +75,7 @@ class SessionEventBridge(
|
|||||||
}
|
}
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
val recentEvents = eventStore.read(sessionId)
|
val recentEvents = events
|
||||||
.mapNotNull { eventToEntry(it) }
|
.mapNotNull { eventToEntry(it) }
|
||||||
.takeLast(7)
|
.takeLast(7)
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
eventStore = module.eventStore,
|
eventStore = module.eventStore,
|
||||||
artifactStore = module.artifactStore,
|
artifactStore = module.artifactStore,
|
||||||
orchestrationRepository = module.orchestrationRepository,
|
orchestrationRepository = module.orchestrationRepository,
|
||||||
approvalRepository = module.approvalRepository,
|
|
||||||
workflowRegistry = module.workflowRegistry,
|
workflowRegistry = module.workflowRegistry,
|
||||||
toolRegistry = module.toolRegistry,
|
toolRegistry = module.toolRegistry,
|
||||||
send = sendFrame,
|
send = sendFrame,
|
||||||
@@ -217,30 +216,35 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val started = ServerMessage.SessionStarted(
|
|
||||||
sessionId = sessionId,
|
|
||||||
workflowId = msg.workflowId,
|
|
||||||
sequence = 0L,
|
|
||||||
sessionSequence = 0L,
|
|
||||||
)
|
|
||||||
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(started)))
|
|
||||||
|
|
||||||
// Emit StageToolManifest for the newly created session (ARCH-TOOL-1)
|
runCatching {
|
||||||
val stages = graph.stages.map { (stageId, stageConfig) ->
|
val started = ServerMessage.SessionStarted(
|
||||||
val tools = stageConfig.allowedTools.mapNotNull { toolName ->
|
|
||||||
module.toolRegistry.resolve(toolName)?.let { tool ->
|
|
||||||
ToolDecl(name = tool.name, tier = tool.tier.level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StageToolDecl(stageId = stageId.value, tools = tools)
|
|
||||||
}
|
|
||||||
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(
|
|
||||||
ServerMessage.StageToolManifest(
|
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
workflowId = msg.workflowId,
|
workflowId = msg.workflowId,
|
||||||
stages = stages,
|
sequence = 0L,
|
||||||
),
|
sessionSequence = 0L,
|
||||||
)))
|
)
|
||||||
|
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(started)))
|
||||||
|
|
||||||
|
// Emit StageToolManifest for the newly created session (ARCH-TOOL-1)
|
||||||
|
val stages = graph.stages.map { (stageId, stageConfig) ->
|
||||||
|
val tools = stageConfig.allowedTools.mapNotNull { toolName ->
|
||||||
|
module.toolRegistry.resolve(toolName)?.let { tool ->
|
||||||
|
ToolDecl(name = tool.name, tier = tool.tier.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StageToolDecl(stageId = stageId.value, tools = tools)
|
||||||
|
}
|
||||||
|
session.send(Frame.Text(ProtocolSerializer.encodeServerMessage(
|
||||||
|
ServerMessage.StageToolManifest(
|
||||||
|
sessionId = sessionId,
|
||||||
|
workflowId = msg.workflowId,
|
||||||
|
stages = stages,
|
||||||
|
),
|
||||||
|
)))
|
||||||
|
}.onFailure { ex ->
|
||||||
|
log.warn("failed to send session start messages for session={}: {}", sessionId.value, ex.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-20
@@ -3,9 +3,6 @@ package com.correx.apps.server.bridge
|
|||||||
import com.correx.apps.server.protocol.EventEntryDto
|
import com.correx.apps.server.protocol.EventEntryDto
|
||||||
import com.correx.apps.server.protocol.ServerMessage
|
import com.correx.apps.server.protocol.ServerMessage
|
||||||
import com.correx.apps.server.registry.WorkflowRegistry
|
import com.correx.apps.server.registry.WorkflowRegistry
|
||||||
import com.correx.core.approvals.ApprovalProjector
|
|
||||||
import com.correx.core.approvals.DefaultApprovalReducer
|
|
||||||
import com.correx.core.approvals.DefaultApprovalRepository
|
|
||||||
import com.correx.core.artifactstore.ArtifactStore
|
import com.correx.core.artifactstore.ArtifactStore
|
||||||
import com.correx.core.events.events.EventMetadata
|
import com.correx.core.events.events.EventMetadata
|
||||||
import com.correx.core.events.events.EventPayload
|
import com.correx.core.events.events.EventPayload
|
||||||
@@ -25,7 +22,6 @@ import com.correx.core.events.types.SessionId
|
|||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.core.events.types.TransitionId
|
import com.correx.core.events.types.TransitionId
|
||||||
import com.correx.core.kernel.orchestration.OrchestrationRepository
|
import com.correx.core.kernel.orchestration.OrchestrationRepository
|
||||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
|
||||||
import com.correx.core.tools.contract.Tool
|
import com.correx.core.tools.contract.Tool
|
||||||
import com.correx.core.tools.registry.ToolRegistry
|
import com.correx.core.tools.registry.ToolRegistry
|
||||||
import com.correx.core.sessions.projections.replay.EventReplayer
|
import com.correx.core.sessions.projections.replay.EventReplayer
|
||||||
@@ -98,13 +94,6 @@ class SessionEventBridgeTest {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private val approvalRepository = DefaultApprovalRepository(
|
|
||||||
DefaultEventReplayer(
|
|
||||||
fakeEventStore(),
|
|
||||||
ApprovalProjector(DefaultApprovalReducer()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val noopWorkflowRegistry = object : WorkflowRegistry {
|
private val noopWorkflowRegistry = object : WorkflowRegistry {
|
||||||
override fun listAll(): List<com.correx.apps.server.registry.WorkflowSummary> = emptyList()
|
override fun listAll(): List<com.correx.apps.server.registry.WorkflowSummary> = emptyList()
|
||||||
override fun find(workflowId: String): com.correx.core.transitions.graph.WorkflowGraph? = null
|
override fun find(workflowId: String): com.correx.core.transitions.graph.WorkflowGraph? = null
|
||||||
@@ -128,7 +117,6 @@ class SessionEventBridgeTest {
|
|||||||
store,
|
store,
|
||||||
noopArtifactStore,
|
noopArtifactStore,
|
||||||
orchRepo,
|
orchRepo,
|
||||||
approvalRepository,
|
|
||||||
noopWorkflowRegistry,
|
noopWorkflowRegistry,
|
||||||
noopToolRegistry,
|
noopToolRegistry,
|
||||||
) { sent.add(it) }
|
) { sent.add(it) }
|
||||||
@@ -158,7 +146,6 @@ class SessionEventBridgeTest {
|
|||||||
store,
|
store,
|
||||||
noopArtifactStore,
|
noopArtifactStore,
|
||||||
orchRepo,
|
orchRepo,
|
||||||
approvalRepository,
|
|
||||||
noopWorkflowRegistry,
|
noopWorkflowRegistry,
|
||||||
noopToolRegistry,
|
noopToolRegistry,
|
||||||
) { sent.add(it) }
|
) { sent.add(it) }
|
||||||
@@ -187,7 +174,6 @@ class SessionEventBridgeTest {
|
|||||||
store,
|
store,
|
||||||
noopArtifactStore,
|
noopArtifactStore,
|
||||||
orchRepo,
|
orchRepo,
|
||||||
approvalRepository,
|
|
||||||
noopWorkflowRegistry,
|
noopWorkflowRegistry,
|
||||||
noopToolRegistry,
|
noopToolRegistry,
|
||||||
) { sent.add(it) }
|
) { sent.add(it) }
|
||||||
@@ -213,7 +199,6 @@ class SessionEventBridgeTest {
|
|||||||
store,
|
store,
|
||||||
noopArtifactStore,
|
noopArtifactStore,
|
||||||
activeOrchestrationRepository(),
|
activeOrchestrationRepository(),
|
||||||
approvalRepository,
|
|
||||||
noopWorkflowRegistry,
|
noopWorkflowRegistry,
|
||||||
noopToolRegistry,
|
noopToolRegistry,
|
||||||
) { sent.add(it) }
|
) { sent.add(it) }
|
||||||
@@ -235,7 +220,7 @@ class SessionEventBridgeTest {
|
|||||||
fun `replaySnapshot with no sessions emits only SnapshotComplete`() = runTest {
|
fun `replaySnapshot with no sessions emits only SnapshotComplete`() = runTest {
|
||||||
val store = fakeEventStore(allEventsList = emptyList())
|
val store = fakeEventStore(allEventsList = emptyList())
|
||||||
val sent = mutableListOf<ServerMessage>()
|
val sent = mutableListOf<ServerMessage>()
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), approvalRepository, noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||||
bridge.replaySnapshot()
|
bridge.replaySnapshot()
|
||||||
assertEquals(1, sent.size)
|
assertEquals(1, sent.size)
|
||||||
assertEquals(ServerMessage.SnapshotComplete, sent[0])
|
assertEquals(ServerMessage.SnapshotComplete, sent[0])
|
||||||
@@ -246,7 +231,7 @@ class SessionEventBridgeTest {
|
|||||||
val events = listOf(storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L))
|
val events = listOf(storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L))
|
||||||
val store = fakeEventStore(allEventsList = events)
|
val store = fakeEventStore(allEventsList = events)
|
||||||
val sent = mutableListOf<ServerMessage>()
|
val sent = mutableListOf<ServerMessage>()
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), approvalRepository, noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||||
bridge.replaySnapshot()
|
bridge.replaySnapshot()
|
||||||
assertEquals(ServerMessage.SnapshotComplete, sent.last())
|
assertEquals(ServerMessage.SnapshotComplete, sent.last())
|
||||||
}
|
}
|
||||||
@@ -261,7 +246,7 @@ class SessionEventBridgeTest {
|
|||||||
override suspend fun lastGlobalSequence(): Long = 5L
|
override suspend fun lastGlobalSequence(): Long = 5L
|
||||||
}
|
}
|
||||||
val sent = mutableListOf<ServerMessage>()
|
val sent = mutableListOf<ServerMessage>()
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), approvalRepository, noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||||
bridge.replaySnapshot()
|
bridge.replaySnapshot()
|
||||||
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
||||||
assertEquals(5L, snapshot.lastSequence)
|
assertEquals(5L, snapshot.lastSequence)
|
||||||
@@ -275,7 +260,7 @@ class SessionEventBridgeTest {
|
|||||||
)
|
)
|
||||||
val store = fakeEventStore(allEventsList = events)
|
val store = fakeEventStore(allEventsList = events)
|
||||||
val sent = mutableListOf<ServerMessage>()
|
val sent = mutableListOf<ServerMessage>()
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), approvalRepository, noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
val bridge = SessionEventBridge(store, noopArtifactStore, activeOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { sent.add(it) }
|
||||||
bridge.replaySnapshot()
|
bridge.replaySnapshot()
|
||||||
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
val snapshot = sent[0] as ServerMessage.SessionSnapshot
|
||||||
assertEquals(3L, snapshot.lastSessionSequence)
|
assertEquals(3L, snapshot.lastSessionSequence)
|
||||||
@@ -293,7 +278,6 @@ class SessionEventBridgeTest {
|
|||||||
store,
|
store,
|
||||||
noopArtifactStore,
|
noopArtifactStore,
|
||||||
activeOrchestrationRepository(),
|
activeOrchestrationRepository(),
|
||||||
approvalRepository,
|
|
||||||
noopWorkflowRegistry,
|
noopWorkflowRegistry,
|
||||||
noopToolRegistry,
|
noopToolRegistry,
|
||||||
) { sent.add(it) }
|
) { sent.add(it) }
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import com.correx.apps.server.bridge.DomainEventMapper
|
|||||||
import com.correx.apps.server.bridge.SessionEventBridge
|
import com.correx.apps.server.bridge.SessionEventBridge
|
||||||
import com.correx.apps.server.protocol.ServerMessage
|
import com.correx.apps.server.protocol.ServerMessage
|
||||||
import com.correx.apps.server.registry.WorkflowRegistry
|
import com.correx.apps.server.registry.WorkflowRegistry
|
||||||
import com.correx.core.approvals.ApprovalProjector
|
|
||||||
import com.correx.core.approvals.DefaultApprovalReducer
|
|
||||||
import com.correx.core.approvals.DefaultApprovalRepository
|
|
||||||
import com.correx.core.artifactstore.ArtifactStore
|
import com.correx.core.artifactstore.ArtifactStore
|
||||||
import com.correx.core.events.events.EventMetadata
|
import com.correx.core.events.events.EventMetadata
|
||||||
import com.correx.core.events.events.EventPayload
|
import com.correx.core.events.events.EventPayload
|
||||||
@@ -21,7 +18,6 @@ import com.correx.core.events.types.EventId
|
|||||||
import com.correx.core.events.types.SessionId
|
import com.correx.core.events.types.SessionId
|
||||||
import com.correx.core.events.types.StageId
|
import com.correx.core.events.types.StageId
|
||||||
import com.correx.core.kernel.orchestration.OrchestrationRepository
|
import com.correx.core.kernel.orchestration.OrchestrationRepository
|
||||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
|
||||||
import com.correx.core.tools.contract.Tool
|
import com.correx.core.tools.contract.Tool
|
||||||
import com.correx.core.tools.registry.ToolRegistry
|
import com.correx.core.tools.registry.ToolRegistry
|
||||||
import com.correx.core.sessions.projections.replay.EventReplayer
|
import com.correx.core.sessions.projections.replay.EventReplayer
|
||||||
@@ -116,14 +112,6 @@ class GlobalStreamHandlerTest {
|
|||||||
override fun all(): List<Tool> = emptyList()
|
override fun all(): List<Tool> = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fakeApprovalRepository(store: EventStore): DefaultApprovalRepository =
|
|
||||||
DefaultApprovalRepository(
|
|
||||||
DefaultEventReplayer(
|
|
||||||
store,
|
|
||||||
ApprovalProjector(DefaultApprovalReducer()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a bridge and a unified send channel so that snapshot messages and live messages
|
* Creates a bridge and a unified send channel so that snapshot messages and live messages
|
||||||
* both flow to the same [received] channel.
|
* both flow to the same [received] channel.
|
||||||
@@ -133,8 +121,7 @@ class GlobalStreamHandlerTest {
|
|||||||
orchRepo: OrchestrationRepository,
|
orchRepo: OrchestrationRepository,
|
||||||
): Pair<SessionEventBridge, Channel<ServerMessage>> {
|
): Pair<SessionEventBridge, Channel<ServerMessage>> {
|
||||||
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
||||||
val approvals = fakeApprovalRepository(store)
|
val bridge = SessionEventBridge(store, noopArtifactStore, orchRepo, noopWorkflowRegistry, noopToolRegistry) { received.send(it) }
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, orchRepo, approvals, noopWorkflowRegistry, noopToolRegistry) { received.send(it) }
|
|
||||||
return bridge to received
|
return bridge to received
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,9 +166,8 @@ class GlobalStreamHandlerTest {
|
|||||||
repeat(100) {
|
repeat(100) {
|
||||||
val liveFlow = MutableSharedFlow<StoredEvent>(extraBufferCapacity = 64)
|
val liveFlow = MutableSharedFlow<StoredEvent>(extraBufferCapacity = 64)
|
||||||
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 5L)
|
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 5L)
|
||||||
val approvals = fakeApprovalRepository(store)
|
|
||||||
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), approvals, noopWorkflowRegistry, noopToolRegistry) {
|
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) {
|
||||||
received.send(it)
|
received.send(it)
|
||||||
}
|
}
|
||||||
val mapper = DomainEventMapper(noopArtifactStore)
|
val mapper = DomainEventMapper(noopArtifactStore)
|
||||||
@@ -222,9 +208,8 @@ class GlobalStreamHandlerTest {
|
|||||||
fun `empty - zero sessions - receives SnapshotComplete then live events`() = runTest {
|
fun `empty - zero sessions - receives SnapshotComplete then live events`() = runTest {
|
||||||
val liveFlow = MutableSharedFlow<StoredEvent>(extraBufferCapacity = 16)
|
val liveFlow = MutableSharedFlow<StoredEvent>(extraBufferCapacity = 16)
|
||||||
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 0L)
|
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 0L)
|
||||||
val approvals = fakeApprovalRepository(store)
|
|
||||||
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
val received = Channel<ServerMessage>(Channel.UNLIMITED)
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), approvals, noopWorkflowRegistry, noopToolRegistry) {
|
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) {
|
||||||
received.send(it)
|
received.send(it)
|
||||||
}
|
}
|
||||||
val mapper = DomainEventMapper(noopArtifactStore)
|
val mapper = DomainEventMapper(noopArtifactStore)
|
||||||
@@ -272,8 +257,7 @@ class GlobalStreamHandlerTest {
|
|||||||
return 0L
|
return 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val approvals = fakeApprovalRepository(store)
|
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { }
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), approvals, noopWorkflowRegistry, noopToolRegistry) { }
|
|
||||||
val mapper = DomainEventMapper(noopArtifactStore)
|
val mapper = DomainEventMapper(noopArtifactStore)
|
||||||
|
|
||||||
val job = launch {
|
val job = launch {
|
||||||
@@ -290,8 +274,7 @@ class GlobalStreamHandlerTest {
|
|||||||
fun `shutdown - cancel outer scope ends subscription with no leaks`() = runTest {
|
fun `shutdown - cancel outer scope ends subscription with no leaks`() = runTest {
|
||||||
val liveFlow = MutableSharedFlow<StoredEvent>()
|
val liveFlow = MutableSharedFlow<StoredEvent>()
|
||||||
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 0L)
|
val store = fakeEventStore(liveFlow, emptySet(), lastGlobal = 0L)
|
||||||
val approvals = fakeApprovalRepository(store)
|
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), noopWorkflowRegistry, noopToolRegistry) { }
|
||||||
val bridge = SessionEventBridge(store, noopArtifactStore, idleOrchestrationRepository(), approvals, noopWorkflowRegistry, noopToolRegistry) { }
|
|
||||||
val mapper = DomainEventMapper(noopArtifactStore)
|
val mapper = DomainEventMapper(noopArtifactStore)
|
||||||
|
|
||||||
val job = launch {
|
val job = launch {
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ object SessionsReducer {
|
|||||||
return if (wfi > 0) {
|
return if (wfi > 0) {
|
||||||
sessions.copy(selectedWorkflowIndex = wfi - 1)
|
sessions.copy(selectedWorkflowIndex = wfi - 1)
|
||||||
} else {
|
} else {
|
||||||
// Move back to last session, or stay at first workflow if no sessions
|
// Move back to last session, or wrap to last workflow if no sessions
|
||||||
val list = filteredSessions(sessions)
|
val list = filteredSessions(sessions)
|
||||||
if (list.isNotEmpty()) {
|
if (list.isNotEmpty()) {
|
||||||
sessions.copy(selectedWorkflowIndex = -1, selectedId = list.last().id)
|
sessions.copy(selectedWorkflowIndex = -1, selectedId = list.last().id)
|
||||||
} else {
|
} else {
|
||||||
sessions.copy(selectedWorkflowIndex = -1)
|
sessions.copy(selectedWorkflowIndex = sessions.workflows.lastIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,8 +487,7 @@ object SessionsReducer {
|
|||||||
clock: () -> Long,
|
clock: () -> Long,
|
||||||
): Pair<SessionsState, List<Effect>> {
|
): Pair<SessionsState, List<Effect>> {
|
||||||
val result = touchSession(sessions, msg.sessionId.value, "COMPLETED", clock)
|
val result = touchSession(sessions, msg.sessionId.value, "COMPLETED", clock)
|
||||||
val cleared = if (msg.sessionId.value == result.selectedId) result.copy(selectedId = null) else result
|
return result to emptyList()
|
||||||
return cleared to emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSessionPausedMessage(
|
private fun processSessionPausedMessage(
|
||||||
|
|||||||
Reference in New Issue
Block a user