fix: wire routerConnected flag, expandable diffs, stage in session list, ArtifactCreated mapping
This commit is contained in:
@@ -5,6 +5,7 @@ import com.correx.apps.server.protocol.RiskSummaryDto
|
||||
import com.correx.apps.server.protocol.ServerMessage
|
||||
import com.correx.core.artifactstore.ArtifactStore
|
||||
import com.correx.core.events.events.ApprovalRequestedEvent
|
||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||
import com.correx.core.events.events.ChatSessionStartedEvent
|
||||
import com.correx.core.events.events.InferenceCompletedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
@@ -157,6 +158,13 @@ suspend fun domainEventToServerMessage(
|
||||
)
|
||||
|
||||
is ApprovalRequestedEvent -> mapApprovalRequested(p, seq, sessionSequence)
|
||||
is ArtifactCreatedEvent -> ServerMessage.ArtifactCreated(
|
||||
sessionId = p.sessionId,
|
||||
stageId = p.stageId,
|
||||
artifactId = p.artifactId,
|
||||
sequence = seq,
|
||||
sessionSequence = sessionSequence,
|
||||
)
|
||||
else -> {
|
||||
log.debug(
|
||||
"DomainEventMapper: unmapped payload type={} sessionId={} sequence={}",
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.correx.core.events.events.InferenceStartedEvent
|
||||
import com.correx.core.events.events.InferenceTimeoutEvent
|
||||
import com.correx.core.events.events.NewEvent
|
||||
import com.correx.core.events.events.ApprovalRequestedEvent
|
||||
import com.correx.core.events.events.ArtifactCreatedEvent
|
||||
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||
import com.correx.core.events.events.StageCompletedEvent
|
||||
@@ -183,6 +184,7 @@ class SessionEventBridge(
|
||||
is OrchestrationPausedEvent -> EventEntryDto(ts, "SessionPaused", p.reason)
|
||||
is WorkflowCompletedEvent -> EventEntryDto(ts, "SessionCompleted", "")
|
||||
is WorkflowFailedEvent -> EventEntryDto(ts, "SessionFailed", p.reason)
|
||||
is ArtifactCreatedEvent -> EventEntryDto(ts, "ArtifactCreated", p.artifactId.value)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.correx.apps.server.protocol
|
||||
|
||||
import com.correx.core.approvals.Tier
|
||||
import com.correx.core.events.types.ApprovalRequestId
|
||||
import com.correx.core.events.types.ArtifactId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import kotlinx.serialization.SerialName
|
||||
@@ -251,6 +252,18 @@ sealed interface ServerMessage {
|
||||
override val sessionSequence: Long? = null,
|
||||
) : ServerMessage, NonEventMessage
|
||||
|
||||
// -- Artifacts --
|
||||
|
||||
@Serializable
|
||||
@SerialName("artifact.created")
|
||||
data class ArtifactCreated(
|
||||
val sessionId: SessionId,
|
||||
val stageId: StageId,
|
||||
val artifactId: ArtifactId,
|
||||
override val sequence: Long,
|
||||
override val sessionSequence: Long,
|
||||
) : ServerMessage, SessionMessage
|
||||
|
||||
// -- Router --
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -21,4 +21,5 @@ sealed class KeyEvent {
|
||||
object CursorRight : KeyEvent()
|
||||
object ToggleWorkflows : KeyEvent()
|
||||
object ToggleSteeringMode : KeyEvent()
|
||||
object ToggleDiffExpand : KeyEvent()
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
val approval = state.selectedPendingApproval()
|
||||
val pendingDecision = state.pendingDecision
|
||||
|
||||
val diffExpanded = state.diffExpanded
|
||||
|
||||
val lines = buildList<Line> {
|
||||
if (approval == null) {
|
||||
add(Line.from(Span.styled("no pending approval", dimStyle)))
|
||||
@@ -43,10 +45,14 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
if (preview != null) {
|
||||
val previewLines = preview.lines()
|
||||
if (previewLines.isNotEmpty()) {
|
||||
val visibleLines = previewLines.take(PREVIEW_MAX_LINES)
|
||||
val visibleLines = if (diffExpanded) {
|
||||
previewLines
|
||||
} else {
|
||||
previewLines.take(PREVIEW_MAX_LINES)
|
||||
}
|
||||
for (line in visibleLines) {
|
||||
if (isDiff) {
|
||||
val truncated = if (line.length > DIFF_LINE_MAX_LENGTH) {
|
||||
val truncated = if (line.length > DIFF_LINE_MAX_LENGTH && !diffExpanded) {
|
||||
line.take(DIFF_LINE_MAX_LENGTH - 3) + "..."
|
||||
} else {
|
||||
line
|
||||
@@ -63,9 +69,12 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
add(Line.from(Span.raw(" $line")))
|
||||
}
|
||||
}
|
||||
if (previewLines.size > PREVIEW_MAX_LINES) {
|
||||
if (!diffExpanded && previewLines.size > PREVIEW_MAX_LINES) {
|
||||
val remaining = previewLines.size - PREVIEW_MAX_LINES
|
||||
add(Line.from(Span.styled(" (preview truncated, $remaining more lines)", dimStyle)))
|
||||
add(Line.from(Span.styled(" (preview truncated, $remaining more lines — ctrl+x to expand)", dimStyle)))
|
||||
}
|
||||
if (diffExpanded) {
|
||||
add(Line.from(Span.styled(" (full diff — ctrl+x to collapse)", dimStyle)))
|
||||
}
|
||||
} else {
|
||||
add(Line.from(Span.styled(" no preview available", dimStyle)))
|
||||
@@ -87,7 +96,7 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
|
||||
// Approve/reject key hints
|
||||
add(Line.from(Span.styled(
|
||||
" ctrl+a approve \u00B7 ctrl+r reject \u00B7 enter submit \u00B7 esc dismiss",
|
||||
" ctrl+a approve \u00B7 ctrl+r reject \u00B7 ctrl+x toggle diff \u00B7 enter submit \u00B7 esc dismiss",
|
||||
dimStyle
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -20,14 +20,13 @@ fun eventHistoryStripWidget(state: TuiState): Paragraph {
|
||||
} else {
|
||||
// Stage ID and status line (compact, dim metadata)
|
||||
val stageInfo = buildString {
|
||||
if (selectedSession.currentStageId != null) {
|
||||
append(selectedSession.currentStageId)
|
||||
val stageId = selectedSession.currentStageId
|
||||
val stage = selectedSession.currentStage
|
||||
when {
|
||||
stageId != null -> append(stageId)
|
||||
stage != null -> append(stage)
|
||||
else -> append("(no stage)")
|
||||
}
|
||||
if (selectedSession.currentStage != null) {
|
||||
if (isNotEmpty()) append(" · ")
|
||||
append(selectedSession.currentStage)
|
||||
}
|
||||
if (isEmpty()) append("(no stage)")
|
||||
append(" ")
|
||||
append(selectedSession.status)
|
||||
}
|
||||
@@ -46,6 +45,7 @@ fun eventHistoryStripWidget(state: TuiState): Paragraph {
|
||||
"StageFailed", "SessionFailed", "ToolFailed", "ToolRejected" -> Style.create().red()
|
||||
"SessionPaused", "InferenceTimeout" -> Style.create().yellow()
|
||||
"StageStarted", "InferenceStarted", "ToolStarted" -> Style.create().blue()
|
||||
"ArtifactCreated" -> Style.create().magenta()
|
||||
else -> Style.create()
|
||||
}
|
||||
val detail = if (ev.detail.length > DETAIL_MAX) {
|
||||
|
||||
@@ -31,7 +31,11 @@ fun sessionListWidget(state: TuiState): Paragraph {
|
||||
val statusSpan = statusSpan(s)
|
||||
val rawName = s.name.ifEmpty { s.workflowId }
|
||||
val truncated = if (rawName.length > NAME_MAX) rawName.take(NAME_MAX - 1) + "…" else rawName
|
||||
add(Line.from(prefix, idSpan, Span.raw(" "), statusSpan, Span.raw(" $truncated")))
|
||||
val nameAndStage = buildString {
|
||||
append(truncated)
|
||||
if (s.currentStageId != null) append(" [${s.currentStageId}]")
|
||||
}
|
||||
add(Line.from(prefix, idSpan, Span.raw(" "), statusSpan, Span.raw(" $nameAndStage")))
|
||||
}
|
||||
if (overflow > 0) {
|
||||
add(Line.from(Span.styled("— $overflow more —", dimStyle)))
|
||||
|
||||
@@ -28,5 +28,6 @@ sealed interface Action {
|
||||
data object CursorRight : Action
|
||||
data object ToggleWorkflows : Action
|
||||
data object CycleChatMode : Action
|
||||
data object ToggleDiffExpand : Action
|
||||
data object NoOp : Action
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ object KeyResolver {
|
||||
KeyEvent.Cancel -> Action.CancelInput
|
||||
KeyEvent.ReturnToSessionList -> Action.ReturnToSessionList
|
||||
KeyEvent.ToggleEventStrip -> Action.ToggleEventStrip
|
||||
KeyEvent.ToggleDiffExpand -> Action.ToggleDiffExpand
|
||||
KeyEvent.Tab -> Action.CycleMode
|
||||
KeyEvent.Backspace -> Action.Backspace
|
||||
is KeyEvent.CharInput -> Action.AppendChar(key.ch)
|
||||
|
||||
@@ -25,6 +25,7 @@ fun mapKey(event: TambouiKeyEvent): KeyEvent? {
|
||||
event.isChar('h') -> KeyEvent.ShowPendingApproval
|
||||
event.isChar('w') -> KeyEvent.ToggleWorkflows
|
||||
event.isChar('s') -> KeyEvent.ToggleSteeringMode
|
||||
event.isChar('x') -> KeyEvent.ToggleDiffExpand
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ object RootReducer {
|
||||
else withBgReset.copy(eventStripVisible = !withBgReset.eventStripVisible)
|
||||
}
|
||||
|
||||
is Action.ToggleDiffExpand -> withBgReset.copy(diffExpanded = !withBgReset.diffExpanded)
|
||||
|
||||
// Input history append on SubmitInput in IN_SESSION + ROUTER (ARCH-HISTORY-1),
|
||||
// or enter a selected session from the list in IDLE.
|
||||
is Action.SubmitInput -> {
|
||||
@@ -167,6 +169,7 @@ object RootReducer {
|
||||
}
|
||||
msg is ServerMessage.RouterResponseMessage -> {
|
||||
withBgReset.copy(
|
||||
routerConnected = true,
|
||||
routerMessages = withBgReset.routerMessages.run {
|
||||
val key = msg.sessionId.value
|
||||
this + (key to (this[key].orEmpty() + msg.content))
|
||||
|
||||
@@ -207,6 +207,7 @@ object SessionsReducer {
|
||||
is ServerMessage.SessionSnapshot -> processSessionSnapshotMessage(clock, sessions, msg)
|
||||
is ServerMessage.ApprovalRequired -> processApprovalRequiredMessage(sessions, msg)
|
||||
is ServerMessage.StageToolManifest -> processStageToolManifestMessage(sessions, msg)
|
||||
is ServerMessage.ArtifactCreated -> processArtifactCreatedMessage(sessions, msg)
|
||||
is ServerMessage.WorkflowList -> processWorkflowListMessage(sessions, msg)
|
||||
else -> sessions to emptyList()
|
||||
}
|
||||
@@ -231,6 +232,7 @@ object SessionsReducer {
|
||||
is ServerMessage.ToolRejected -> msg.sessionId.value
|
||||
is ServerMessage.ApprovalRequired -> msg.sessionId.value
|
||||
is ServerMessage.SessionSnapshot -> msg.sessionId.value
|
||||
is ServerMessage.ArtifactCreated -> msg.sessionId.value
|
||||
// Control/non-event messages do not count per ARCH-BADGE-1:
|
||||
is ServerMessage.StageToolManifest -> null
|
||||
is ServerMessage.SnapshotComplete -> null
|
||||
@@ -627,6 +629,30 @@ object SessionsReducer {
|
||||
},
|
||||
)
|
||||
|
||||
private fun processArtifactCreatedMessage(
|
||||
sessions: SessionsState,
|
||||
msg: ServerMessage.ArtifactCreated,
|
||||
): Pair<SessionsState, List<Effect>> {
|
||||
val now = msg.sequence
|
||||
val entry = TuiEventEntry(
|
||||
timestamp = formatTime(now),
|
||||
type = "ArtifactCreated",
|
||||
detail = msg.artifactId.value,
|
||||
)
|
||||
return sessions.copy(
|
||||
sessions = sessions.sessions.map { s ->
|
||||
if (s.id == msg.sessionId.value) {
|
||||
s.copy(
|
||||
recentEvents = (s.recentEvents + entry).takeLast(7),
|
||||
lastEventAt = now,
|
||||
)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
},
|
||||
) to emptyList()
|
||||
}
|
||||
|
||||
private fun processWorkflowListMessage(
|
||||
sessions: SessionsState,
|
||||
msg: ServerMessage.WorkflowList,
|
||||
|
||||
@@ -47,6 +47,7 @@ data class TuiState(
|
||||
val providerType: ProviderType = ProviderType.LOCAL,
|
||||
val routerConnected: Boolean = false,
|
||||
val routerMessages: Map<String, List<String>> = emptyMap(),
|
||||
val diffExpanded: Boolean = false,
|
||||
val chatMode: ChatMode = ChatMode.CHAT,
|
||||
/**
|
||||
* True from connect until SnapshotComplete observed. Reset to true on Disconnected.
|
||||
|
||||
Reference in New Issue
Block a user