feat(tui-go): health-checks pane

Surface the health subsystem (llama/event-store/disk probes) in the TUI,
mirroring the session-stats pane: ClientMessage.GetHealthChecks ->
ServerMessage.HealthChecks (health.checks, reuses HealthReport) ->
StreamQueries.healthChecks via HealthInspectionService; Go OverlayHealth on
key H + palette 'health checks', pull-based fetch, per-subject status with
degraded loud. Empty/disabled -> visible fallback (no blank/lie). Go+Kotlin
golden tests pin the wire contract field-for-field. Observability spec §4/§3.
This commit is contained in:
2026-06-14 18:57:51 +04:00
parent 09f05549a0
commit fd67a6c68d
13 changed files with 285 additions and 3 deletions
@@ -164,6 +164,39 @@ func TestDecodeGoldenServerFrames(t *testing.T) {
}
},
},
{
name: "health.checks carries nested health report",
json: `{"type":"health.checks","health":{"overall":"DEGRADED","subjects":[` +
`{"subject":"DISK","status":"DEGRADED","metric":"free_bytes","observedValue":512000000,` +
`"detail":"disk free below 1 GB threshold","since":"2026-06-14T10:00:00Z"},` +
`{"subject":"EVENT_STORE","status":"HEALTHY","metric":"write_latency_ms","observedValue":4,` +
`"detail":"ok","since":"2026-06-14T09:00:00Z"}],` +
`"checkedAt":"2026-06-14T10:05:00Z"}}`,
wantType: TypeHealthChecks,
eventBear: false,
check: func(t *testing.T, m ServerMessage) {
if m.Health == nil {
t.Fatalf("health.checks: Health is nil")
}
if m.Health.Overall != "DEGRADED" {
t.Fatalf("health.checks overall = %q, want DEGRADED", m.Health.Overall)
}
if len(m.Health.Subjects) != 2 {
t.Fatalf("health.checks subjects len = %d, want 2", len(m.Health.Subjects))
}
disk := m.Health.Subjects[0]
if disk.Subject != "DISK" || disk.Status != "DEGRADED" || disk.ObservedValue != 512000000 {
t.Fatalf("health.checks DISK subject: %+v", disk)
}
es := m.Health.Subjects[1]
if es.Subject != "EVENT_STORE" || es.Status != "HEALTHY" {
t.Fatalf("health.checks EVENT_STORE subject: %+v", es)
}
if m.Health.CheckedAt != "2026-06-14T10:05:00Z" {
t.Fatalf("health.checks checkedAt = %q", m.Health.CheckedAt)
}
},
},
{
name: "workflow.proposed carries candidate workflows",
json: `{"type":"workflow.proposed","sessionId":"s1","proposalId":"prop-1","prompt":"Run one?",` +
@@ -248,6 +281,11 @@ func TestEncodeGoldenClientFrames(t *testing.T) {
}
},
},
{
name: "GetHealthChecks encodes correctly",
frame: GetHealthChecks(),
wantType: clientPrefix + "GetHealthChecks",
},
{
name: "DiscardIdea carries the ideaId",
frame: DiscardIdea("i1"),
+29
View File
@@ -55,6 +55,7 @@ const (
TypeConfigSnapshot = "config.snapshot"
TypeSessionStats = "session.stats"
TypeIdeaList = "idea.list"
TypeHealthChecks = "health.checks"
)
// ServerMessage is a flat decode of every server->client variant. Field names
@@ -139,6 +140,9 @@ type ServerMessage struct {
// session.stats — derived metrics for a session (reply to GetSessionStats)
Stats *StatsDto `json:"stats"`
// health.checks — system health report (reply to GetHealthChecks)
Health *HealthDto `json:"health"`
// clarification.required — open questions a stage raised for the operator
Questions []ClarificationQuestionDto `json:"questions"`
@@ -242,6 +246,26 @@ type FailureMetricsDto struct {
WorkflowFailures int64 `json:"workflowFailures"`
}
// HealthSubjectDto is one monitored subject's health status. Mirrors Kotlin HealthSubjectReport.
// status is "HEALTHY" or "DEGRADED"; since is an ISO-8601 string for the last edge event.
type HealthSubjectDto struct {
Subject string `json:"subject"`
Status string `json:"status"`
Metric string `json:"metric"`
ObservedValue int64 `json:"observedValue"`
Detail string `json:"detail"`
Since string `json:"since"`
}
// HealthDto is the system health snapshot. Mirrors Kotlin HealthReport.
// overall is "HEALTHY" or "DEGRADED". subjects is empty when health monitoring is disabled
// or no probes have fired yet — the TUI renders a visible fallback in that case.
type HealthDto struct {
Overall string `json:"overall"`
Subjects []HealthSubjectDto `json:"subjects"`
CheckedAt string `json:"checkedAt"`
}
type ConfigFieldDto struct {
Key string `json:"key"`
Type string `json:"type"`
@@ -465,3 +489,8 @@ func CreateGrant(sessionID, scope string, permittedTiers []string, reason, toolN
func Hello(workingDir string) []byte {
return encode("Hello", map[string]any{"workingDir": workingDir})
}
// GetHealthChecks requests the system health-check report (replied to with health.checks).
func GetHealthChecks() []byte {
return encode("GetHealthChecks", map[string]any{})
}