feat(server): workflow.proposed WS frame
Map WorkflowProposedEvent to a workflow.proposed ServerMessage carrying the candidates + original request. No new ClientMessage: a pick reuses StartSession (launch the chosen workflow seeded with the request) and the manual-answer slot reuses ChatInput.
This commit is contained in:
@@ -41,6 +41,7 @@ import com.correx.core.events.events.TransitionExecutedEvent
|
||||
import com.correx.core.events.events.WorkflowCompletedEvent
|
||||
import com.correx.core.events.events.RouterNarrationEvent
|
||||
import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowProposedEvent
|
||||
import com.correx.core.events.risk.RiskAction
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -230,6 +231,15 @@ suspend fun domainEventToServerMessage(
|
||||
sequence = seq,
|
||||
sessionSequence = sessionSequence,
|
||||
)
|
||||
is WorkflowProposedEvent -> ServerMessage.WorkflowProposed(
|
||||
sessionId = p.sessionId,
|
||||
proposalId = p.proposalId,
|
||||
prompt = p.prompt,
|
||||
candidates = p.candidates,
|
||||
originalRequest = p.originalRequest,
|
||||
sequence = seq,
|
||||
sessionSequence = sessionSequence,
|
||||
)
|
||||
is ApprovalDecisionResolvedEvent -> ServerMessage.ApprovalResolved(
|
||||
// ApprovalDecisionResolvedEvent has no sessionId on its payload — read it from the event envelope
|
||||
sessionId = event.metadata.sessionId,
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.correx.apps.server.protocol
|
||||
import com.correx.apps.server.metrics.MetricsReport
|
||||
import com.correx.core.approvals.Tier
|
||||
import com.correx.core.events.events.ClarificationQuestion
|
||||
import com.correx.core.events.events.ProposedWorkflow
|
||||
import com.correx.core.events.types.ApprovalRequestId
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.events.types.ClarificationRequestId
|
||||
@@ -323,6 +324,25 @@ sealed interface ServerMessage {
|
||||
override val sessionSequence: Long,
|
||||
) : ServerMessage, SessionMessage
|
||||
|
||||
/**
|
||||
* The router triaged a chat request to one or more candidate workflows (from a
|
||||
* WorkflowProposedEvent). The client renders [candidates] as a choice panel; picking one launches
|
||||
* that workflow via ClientMessage.StartSession seeded with [originalRequest], while the panel's
|
||||
* manual-answer slot continues the conversation via ClientMessage.ChatInput. Non-authoritative:
|
||||
* nothing runs until the operator picks.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("workflow.proposed")
|
||||
data class WorkflowProposed(
|
||||
val sessionId: SessionId,
|
||||
val proposalId: String,
|
||||
val prompt: String,
|
||||
val candidates: List<ProposedWorkflow>,
|
||||
val originalRequest: String,
|
||||
override val sequence: Long,
|
||||
override val sessionSequence: Long,
|
||||
) : ServerMessage, SessionMessage
|
||||
|
||||
// -- System / infra --
|
||||
|
||||
@Serializable
|
||||
|
||||
+26
@@ -216,6 +216,32 @@ class ServerMessageSerializationTest {
|
||||
assert(jsonStr.contains("\"React\"")) { "expected options" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WorkflowProposed encodes type, candidates and original request`() {
|
||||
val msg = ServerMessage.WorkflowProposed(
|
||||
sessionId = SessionId("sess-w"),
|
||||
proposalId = "prop-1",
|
||||
prompt = "Run one of these?",
|
||||
candidates = listOf(
|
||||
com.correx.core.events.events.ProposedWorkflow(workflowId = "research", reason = "gather + report"),
|
||||
com.correx.core.events.events.ProposedWorkflow(workflowId = "role_pipeline"),
|
||||
),
|
||||
originalRequest = "find papers on event sourcing",
|
||||
sequence = 9,
|
||||
sessionSequence = 4,
|
||||
)
|
||||
val jsonStr = ProtocolSerializer.encodeServerMessage(msg)
|
||||
assert(jsonStr.contains("\"type\":\"workflow.proposed\"")) { "expected type=workflow.proposed" }
|
||||
assert(jsonStr.contains("\"workflowId\":\"research\"")) { "expected candidate id" }
|
||||
assert(jsonStr.contains("\"originalRequest\":\"find papers on event sourcing\"")) { "expected original request" }
|
||||
|
||||
val decoded = json.decodeFromString<ServerMessage.WorkflowProposed>(jsonStr)
|
||||
assertEquals(2, decoded.candidates.size)
|
||||
assertEquals("research", decoded.candidates.first().workflowId)
|
||||
assertEquals("gather + report", decoded.candidates.first().reason)
|
||||
assertEquals("", decoded.candidates[1].reason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ClarificationResponse decodes from the client wire format`() {
|
||||
val wire = """
|
||||
|
||||
Reference in New Issue
Block a user