feat(server): intent input channel — StartSession.input seeds decision journal

This commit is contained in:
2026-06-04 02:16:23 +04:00
parent 6393d578fd
commit fe561ada09
7 changed files with 63 additions and 2 deletions
@@ -24,7 +24,13 @@ enum class GrantScopeDto {
@Serializable
sealed class ClientMessage {
@Serializable
data class StartSession(val workflowId: String, val config: SessionConfigDto?) : ClientMessage()
data class StartSession(
val workflowId: String,
val config: SessionConfigDto?,
// Optional freeform request for intent-driven pipelines. Seeded into the decision
// journal so every stage sees it. Fixed-task workflows (e.g. healthcheck) omit it.
val input: String? = null,
) : ClientMessage()
@Serializable
data class ResumeSession(val sessionId: SessionId) : ClientMessage()
@@ -17,6 +17,7 @@ import com.correx.core.approvals.Tier
import com.correx.core.events.events.ApprovalGrantCreatedEvent
import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.InitialIntentEvent
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.SessionWorkspaceBoundEvent
import com.correx.core.events.events.StoredEvent
@@ -466,6 +467,22 @@ class GlobalStreamHandler(private val module: ServerModule) {
// the bound workspace root for tool path containment and per-session tool registry.
val sessionConfig = module.defaultOrchestrationConfig.copy(workspace = resolvedWorkspace)
// Seed the freeform request (if any) before the run so it lands in the decision
// journal and is pinned into every stage's context (the intent input channel).
msg.input?.takeIf { it.isNotBlank() }?.let { intent ->
module.eventStore.append(NewEvent(
metadata = EventMetadata(
eventId = EventId(UUID.randomUUID().toString()),
sessionId = sessionId,
timestamp = Clock.System.now(),
schemaVersion = 1,
causationId = null,
correlationId = null,
),
payload = InitialIntentEvent(sessionId = sessionId, intent = intent),
))
}
// Only launch orchestrator after protocol messages are delivered successfully.
// Uses launchSessionRun() so the job is tracked in activeSessionJobs — this prevents
// the OrchestrationResumedEvent subscription from spuriously launching a duplicate resume.