Trenne Lifecycle und Server-Connectivity im Agenten

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jesko Anschütz 2026-03-22 18:22:23 +01:00
parent 896eade0fb
commit 9ee24fe4ae
2 changed files with 79 additions and 23 deletions

View file

@ -14,14 +14,21 @@ import (
type Status string type Status string
type Connectivity string
const ( const (
StatusStarting Status = "starting" StatusStarting Status = "starting"
StatusRunning Status = "running" StatusRunning Status = "running"
StatusStopped Status = "stopped" StatusStopped Status = "stopped"
ConnectivityUnknown Connectivity = "unknown"
ConnectivityOnline Connectivity = "online"
ConnectivityDegraded Connectivity = "degraded"
) )
type HealthSnapshot struct { type HealthSnapshot struct {
Status Status Status Status
ServerConnectivity Connectivity
ScreenID string ScreenID string
ServerBaseURL string ServerBaseURL string
MQTTBroker string MQTTBroker string
@ -38,6 +45,7 @@ type App struct {
mu sync.RWMutex mu sync.RWMutex
status Status status Status
serverConnectivity Connectivity
startedAt time.Time startedAt time.Time
lastHeartbeatAt time.Time lastHeartbeatAt time.Time
} }
@ -72,6 +80,7 @@ func newApp(cfg config.Config, logger *log.Logger, now func() time.Time, reporte
now: now, now: now,
reporter: reporter, reporter: reporter,
status: StatusStarting, status: StatusStarting,
serverConnectivity: ConnectivityUnknown,
} }
} }
@ -81,6 +90,7 @@ func (a *App) Snapshot() HealthSnapshot {
return HealthSnapshot{ return HealthSnapshot{
Status: a.status, Status: a.status,
ServerConnectivity: a.serverConnectivity,
ScreenID: a.Config.ScreenID, ScreenID: a.Config.ScreenID,
ServerBaseURL: a.Config.ServerBaseURL, ServerBaseURL: a.Config.ServerBaseURL,
MQTTBroker: a.Config.MQTTBroker, MQTTBroker: a.Config.MQTTBroker,
@ -170,9 +180,15 @@ func (a *App) reportStatus(ctx context.Context) {
LastHeartbeatAt: snapshot.LastHeartbeatAt, LastHeartbeatAt: snapshot.LastHeartbeatAt,
}) })
if err != nil { if err != nil {
a.mu.Lock()
a.serverConnectivity = ConnectivityDegraded
a.mu.Unlock()
a.logger.Printf("event=status_report_failed screen_id=%s error=%v", a.Config.ScreenID, err) a.logger.Printf("event=status_report_failed screen_id=%s error=%v", a.Config.ScreenID, err)
return return
} }
a.mu.Lock()
a.serverConnectivity = ConnectivityOnline
a.mu.Unlock()
a.logger.Printf("event=status_report_sent screen_id=%s", a.Config.ScreenID) a.logger.Printf("event=status_report_sent screen_id=%s", a.Config.ScreenID)
} }

View file

@ -119,6 +119,10 @@ func TestAppSnapshotIncludesConfiguredTargets(t *testing.T) {
if got, want := snapshot.HeartbeatEvery, 15; got != want { if got, want := snapshot.HeartbeatEvery, 15; got != want {
t.Fatalf("HeartbeatEvery = %d, want %d", got, want) t.Fatalf("HeartbeatEvery = %d, want %d", got, want)
} }
if got, want := snapshot.ServerConnectivity, ConnectivityUnknown; got != want {
t.Fatalf("ServerConnectivity = %q, want %q", got, want)
}
} }
func TestAppRunWithCanceledContextDoesNotLogConfiguredOrHeartbeat(t *testing.T) { func TestAppRunWithCanceledContextDoesNotLogConfiguredOrHeartbeat(t *testing.T) {
@ -195,4 +199,40 @@ func TestAppRunReportsStatusWithoutStoppingOnReporterError(t *testing.T) {
if !strings.Contains(logs, "event=status_report_failed") { if !strings.Contains(logs, "event=status_report_failed") {
t.Fatalf("logs missing status_report_failed event: %s", logs) t.Fatalf("logs missing status_report_failed event: %s", logs)
} }
if got, want := application.Snapshot().ServerConnectivity, ConnectivityDegraded; got != want {
t.Fatalf("ServerConnectivity = %q, want %q", got, want)
}
}
func TestAppRunMarksServerConnectivityOnlineAfterSuccessfulReport(t *testing.T) {
application := newApp(config.Config{
ScreenID: "screen-online",
ServerBaseURL: "http://127.0.0.1:8080",
MQTTBroker: "tcp://127.0.0.1:1883",
HeartbeatEvery: 1,
StatusReportEvery: 1,
}, log.New(&bytes.Buffer{}, "", 0), time.Now, &recordingReporter{})
ctx, cancel := context.WithCancel(context.Background())
errCh := make(chan error, 1)
go func() {
errCh <- application.Run(ctx)
}()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if application.Snapshot().ServerConnectivity == ConnectivityOnline {
break
}
time.Sleep(10 * time.Millisecond)
}
if got, want := application.Snapshot().ServerConnectivity, ConnectivityOnline; got != want {
cancel()
t.Fatalf("ServerConnectivity = %q, want %q", got, want)
}
cancel()
<-errCh
} }