feat(server,tui): session stats pane — metrics over the WS bus + Bubble Tea overlay
Surfaces the observability metrics (observability-spec §3-tier-2) in the Go TUI: a session-summary pane showing duration, token throughput per provider, tool time, approval latency per tier, failure counts, and the session wall-time accounting split. Press `S` (or palette → "session stats"). Mirrors the artifacts/config fetch pattern exactly — a WS request/response, not a new transport. Server-side reuses the already-tested MetricsInspectionService (one MetricsReport definition, two wires: REST `correx stats` + WS). Server: - ClientMessage.GetSessionStats + ServerMessage.SessionStats(@SerialName session.stats), reusing MetricsReport as the nested payload. - StreamQueries.sessionStats replays via MetricsInspectionService (pure read, replay-neutral); routed in GlobalStreamHandler. - ServerMessageSerializationTest golden pins the session.stats wire format. TUI (Go/Bubble Tea): - protocol: TypeSessionStats + StatsDto/nested structs + GetSessionStats encoder + golden decode test (cross-language contract). - OverlayStats pane (statsModal), `S` key + palette entry + footer hint, loading/cache-by-session state, bounded breakdown rows. - demo `stats` preview case for serverless visual verification.
This commit is contained in:
@@ -169,6 +169,8 @@ func (m Model) handleNormalKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.overlay = OverlayToolPalette
|
||||
case "v":
|
||||
m.openArtifacts()
|
||||
case "S":
|
||||
m.openStats()
|
||||
case "g":
|
||||
m.openConfig()
|
||||
case "m":
|
||||
@@ -429,6 +431,10 @@ func (m Model) handleOverlayKey(k tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case runeIs(k, "m"):
|
||||
m.overlay = OverlayNone
|
||||
}
|
||||
case OverlayStats:
|
||||
if runeIs(k, "S") {
|
||||
m.overlay = OverlayNone
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -450,6 +456,21 @@ func (m *Model) openArtifacts() {
|
||||
m.client.Send(protocol.ListArtifacts(m.selectedID))
|
||||
}
|
||||
|
||||
// openStats opens the session-stats pane for the selected session and requests its
|
||||
// metrics from the server. No-op when no session is selected.
|
||||
func (m *Model) openStats() {
|
||||
if m.selectedID == "" {
|
||||
return
|
||||
}
|
||||
m.overlay = OverlayStats
|
||||
// Reuse a cached report only if it's for this session; otherwise show a loading state.
|
||||
if m.statsFor != m.selectedID {
|
||||
m.stats = nil
|
||||
m.statsLoading = true
|
||||
}
|
||||
m.client.Send(protocol.GetSessionStats(m.selectedID))
|
||||
}
|
||||
|
||||
// openModelsOverlay opens the model picker, pre-selecting the resident model.
|
||||
func (m *Model) openModelsOverlay() {
|
||||
m.overlay = OverlayModels
|
||||
@@ -502,6 +523,7 @@ func paletteCommands() []paletteCmd {
|
||||
{"models", "swap model", "pick / pin the local model"},
|
||||
{"events", "event inspector", "browse the event stream"},
|
||||
{"artifacts", "view artifacts", "browse this session's artifacts"},
|
||||
{"stats", "session stats", "metrics for the selected session"},
|
||||
{"config", "edit config", "view / change correx settings"},
|
||||
{"mode", "toggle mode", "switch chat / steering"},
|
||||
{"cancel", "cancel session", "stop the selected session"},
|
||||
@@ -539,6 +561,8 @@ func (m Model) execPalette(id string) (tea.Model, tea.Cmd) {
|
||||
m.overlayEventIdx = 0
|
||||
case "artifacts":
|
||||
m.openArtifacts()
|
||||
case "stats":
|
||||
m.openStats()
|
||||
case "config":
|
||||
m.openConfig()
|
||||
case "mode":
|
||||
|
||||
Reference in New Issue
Block a user