feat(tui): event-derived ordering + retire Kotlin TUI

Rebuild the Go TUI transcript from the event stream instead of
optimistic local echoes. ChatTurnEvent now maps to ServerMessage.ChatTurn
(chat.turn); the TUI auto-vivifies sessions and renders user+router turns
from events. SessionStarted is de-special-cased into SessionAnnounced
(still carries workflowId), dropping the router.response shadow and
optimistic echoes.

Delete the unused Kotlin TUI (apps/tui) and add a kotlinx<->Go
golden-fixture test to lock the wire protocol. Gitignore server runtime
logs.
This commit is contained in:
2026-06-02 23:38:20 +04:00
parent b2f60d09bb
commit 1017bfffef
60 changed files with 231 additions and 4813 deletions
+6 -3
View File
@@ -1,7 +1,7 @@
// Package protocol implements the correx WebSocket wire contract used to talk
// to apps/server over /stream. The server (Kotlin/kotlinx.serialization) uses a
// "type" class discriminator. Server->client variants carry @SerialName values
// (e.g. "session.started"); client->server variants have no @SerialName, so the
// (e.g. "session.announced"); client->server variants have no @SerialName, so the
// discriminator is the fully-qualified Kotlin class name.
package protocol
@@ -13,7 +13,8 @@ const clientPrefix = "com.correx.apps.server.protocol.ClientMessage."
// Server message discriminator values (@SerialName on ServerMessage variants).
const (
TypeSessionStarted = "session.started"
TypeSessionAnnounced = "session.announced"
TypeChatTurn = "chat.turn"
TypeSessionPaused = "session.paused"
TypeSessionResumed = "session.resumed"
TypeSessionCompleted = "session.completed"
@@ -54,6 +55,8 @@ type ServerMessage struct {
WorkflowID string `json:"workflowId"`
StageID string `json:"stageId"`
ToolName string `json:"toolName"`
Role string `json:"role"`
TurnID string `json:"turnId"`
Reason string `json:"reason"`
Summary string `json:"outputSummary"`
Response string `json:"responseText"`
@@ -168,7 +171,7 @@ func Decode(raw []byte) (ServerMessage, error) {
// and are applied immediately.
func (m ServerMessage) IsEventBearing() bool {
switch m.Type {
case TypeSessionStarted, TypeSessionPaused, TypeSessionResumed,
case TypeSessionAnnounced, TypeChatTurn, TypeSessionPaused, TypeSessionResumed,
TypeSessionCompleted, TypeSessionFailed,
TypeStageStarted, TypeStageCompleted, TypeStageFailed,
TypeInferenceStarted, TypeInferenceDone, TypeInferenceTimeout,