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.WorkflowCompletedEvent
|
||||||
import com.correx.core.events.events.RouterNarrationEvent
|
import com.correx.core.events.events.RouterNarrationEvent
|
||||||
import com.correx.core.events.events.WorkflowFailedEvent
|
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.risk.RiskAction
|
||||||
import com.correx.core.events.types.ArtifactId
|
import com.correx.core.events.types.ArtifactId
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -230,6 +231,15 @@ suspend fun domainEventToServerMessage(
|
|||||||
sequence = seq,
|
sequence = seq,
|
||||||
sessionSequence = sessionSequence,
|
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(
|
is ApprovalDecisionResolvedEvent -> ServerMessage.ApprovalResolved(
|
||||||
// ApprovalDecisionResolvedEvent has no sessionId on its payload — read it from the event envelope
|
// ApprovalDecisionResolvedEvent has no sessionId on its payload — read it from the event envelope
|
||||||
sessionId = event.metadata.sessionId,
|
sessionId = event.metadata.sessionId,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.correx.apps.server.protocol
|
|||||||
import com.correx.apps.server.metrics.MetricsReport
|
import com.correx.apps.server.metrics.MetricsReport
|
||||||
import com.correx.core.approvals.Tier
|
import com.correx.core.approvals.Tier
|
||||||
import com.correx.core.events.events.ClarificationQuestion
|
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.ApprovalRequestId
|
||||||
import com.correx.core.events.types.ArtifactId
|
import com.correx.core.events.types.ArtifactId
|
||||||
import com.correx.core.events.types.ClarificationRequestId
|
import com.correx.core.events.types.ClarificationRequestId
|
||||||
@@ -323,6 +324,25 @@ sealed interface ServerMessage {
|
|||||||
override val sessionSequence: Long,
|
override val sessionSequence: Long,
|
||||||
) : ServerMessage, SessionMessage
|
) : 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 --
|
// -- System / infra --
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
+26
@@ -216,6 +216,32 @@ class ServerMessageSerializationTest {
|
|||||||
assert(jsonStr.contains("\"React\"")) { "expected options" }
|
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
|
@Test
|
||||||
fun `ClarificationResponse decodes from the client wire format`() {
|
fun `ClarificationResponse decodes from the client wire format`() {
|
||||||
val wire = """
|
val wire = """
|
||||||
|
|||||||
Reference in New Issue
Block a user