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:
@@ -51,6 +51,7 @@ const (
|
||||
TypeRouterNarration = "router.narration"
|
||||
TypeArtifactList = "artifact.list"
|
||||
TypeConfigSnapshot = "config.snapshot"
|
||||
TypeSessionStats = "session.stats"
|
||||
)
|
||||
|
||||
// ServerMessage is a flat decode of every server->client variant. Field names
|
||||
@@ -131,6 +132,68 @@ type ServerMessage struct {
|
||||
ConfigFields []ConfigFieldDto `json:"fields"`
|
||||
ConfigRestartRequired []string `json:"restartRequired"`
|
||||
ConfigError *string `json:"error"`
|
||||
|
||||
// session.stats — derived metrics for a session (reply to GetSessionStats)
|
||||
Stats *StatsDto `json:"stats"`
|
||||
}
|
||||
|
||||
// StatsDto mirrors the server's MetricsReport: a session's derived metrics
|
||||
// (observability-spec §2/§3). Raw sums plus the derived ratios the pane renders.
|
||||
type StatsDto struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
EventCount int64 `json:"eventCount"`
|
||||
SessionDurationMs int64 `json:"sessionDurationMs"`
|
||||
InferenceCount int64 `json:"inferenceCount"`
|
||||
InferenceMs int64 `json:"inferenceMs"`
|
||||
PromptTokens int64 `json:"promptTokens"`
|
||||
CompletionTokens int64 `json:"completionTokens"`
|
||||
TokensPerSecond float64 `json:"tokensPerSecond"`
|
||||
PerProvider []ProviderStatsDto `json:"perProvider"`
|
||||
ToolCount int64 `json:"toolCount"`
|
||||
ToolMs int64 `json:"toolMs"`
|
||||
PerTool []ToolStatsDto `json:"perTool"`
|
||||
ApprovalsRequested int64 `json:"approvalsRequested"`
|
||||
ApprovalsResolved int64 `json:"approvalsResolved"`
|
||||
ApprovalsPending int64 `json:"approvalsPending"`
|
||||
ApprovalWaitMs int64 `json:"approvalWaitMs"`
|
||||
AvgApprovalWaitMs int64 `json:"avgApprovalWaitMs"`
|
||||
PerTier []TierApprovalStatsDto `json:"perTier"`
|
||||
Failures FailureMetricsDto `json:"failures"`
|
||||
InferencePct float64 `json:"inferencePct"`
|
||||
ToolPct float64 `json:"toolPct"`
|
||||
ApprovalWaitPct float64 `json:"approvalWaitPct"`
|
||||
}
|
||||
|
||||
type ProviderStatsDto struct {
|
||||
Provider string `json:"provider"`
|
||||
CompletedCount int64 `json:"completedCount"`
|
||||
TotalLatencyMs int64 `json:"totalLatencyMs"`
|
||||
PromptTokens int64 `json:"promptTokens"`
|
||||
CompletionTokens int64 `json:"completionTokens"`
|
||||
TokensPerSecond float64 `json:"tokensPerSecond"`
|
||||
}
|
||||
|
||||
type ToolStatsDto struct {
|
||||
ToolName string `json:"toolName"`
|
||||
CompletedCount int64 `json:"completedCount"`
|
||||
TotalDurationMs int64 `json:"totalDurationMs"`
|
||||
}
|
||||
|
||||
type TierApprovalStatsDto struct {
|
||||
Tier string `json:"tier"`
|
||||
RequestedCount int64 `json:"requestedCount"`
|
||||
ResolvedCount int64 `json:"resolvedCount"`
|
||||
TotalWaitMs int64 `json:"totalWaitMs"`
|
||||
AvgWaitMs int64 `json:"avgWaitMs"`
|
||||
}
|
||||
|
||||
type FailureMetricsDto struct {
|
||||
InferenceFailures int64 `json:"inferenceFailures"`
|
||||
InferenceTimeouts int64 `json:"inferenceTimeouts"`
|
||||
ToolFailures int64 `json:"toolFailures"`
|
||||
ToolRejections int64 `json:"toolRejections"`
|
||||
StageFailures int64 `json:"stageFailures"`
|
||||
WorkflowFailures int64 `json:"workflowFailures"`
|
||||
}
|
||||
|
||||
type ConfigFieldDto struct {
|
||||
@@ -275,6 +338,11 @@ func ListArtifacts(sessionID string) []byte {
|
||||
return encode("ListArtifacts", map[string]any{"sessionId": sessionID})
|
||||
}
|
||||
|
||||
// GetSessionStats asks the server for a session's derived metrics (replied to with session.stats).
|
||||
func GetSessionStats(sessionID string) []byte {
|
||||
return encode("GetSessionStats", map[string]any{"sessionId": sessionID})
|
||||
}
|
||||
|
||||
// GetConfig requests the current editable config (replied to with a config.snapshot).
|
||||
func GetConfig() []byte {
|
||||
return encode("GetConfig", map[string]any{})
|
||||
|
||||
Reference in New Issue
Block a user