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,20 +14,27 @@ import (
type Status string
type Connectivity string
const (
StatusStarting Status = "starting"
StatusRunning Status = "running"
StatusStopped Status = "stopped"
ConnectivityUnknown Connectivity = "unknown"
ConnectivityOnline Connectivity = "online"
ConnectivityDegraded Connectivity = "degraded"
)
type HealthSnapshot struct {
Status Status
ScreenID string
ServerBaseURL string
MQTTBroker string
HeartbeatEvery int
StartedAt time.Time
LastHeartbeatAt time.Time
Status Status
ServerConnectivity Connectivity
ScreenID string
ServerBaseURL string
MQTTBroker string
HeartbeatEvery int
StartedAt time.Time
LastHeartbeatAt time.Time
}
type App struct {
@ -36,10 +43,11 @@ type App struct {
now func() time.Time
reporter statusSender
mu sync.RWMutex
status Status
startedAt time.Time
lastHeartbeatAt time.Time
mu sync.RWMutex
status Status
serverConnectivity Connectivity
startedAt time.Time
lastHeartbeatAt time.Time
}
type statusSender interface {
@ -67,11 +75,12 @@ func newApp(cfg config.Config, logger *log.Logger, now func() time.Time, reporte
}
return &App{
Config: cfg,
logger: logger,
now: now,
reporter: reporter,
status: StatusStarting,
Config: cfg,
logger: logger,
now: now,
reporter: reporter,
status: StatusStarting,
serverConnectivity: ConnectivityUnknown,
}
}
@ -80,13 +89,14 @@ func (a *App) Snapshot() HealthSnapshot {
defer a.mu.RUnlock()
return HealthSnapshot{
Status: a.status,
ScreenID: a.Config.ScreenID,
ServerBaseURL: a.Config.ServerBaseURL,
MQTTBroker: a.Config.MQTTBroker,
HeartbeatEvery: a.Config.HeartbeatEvery,
StartedAt: a.startedAt,
LastHeartbeatAt: a.lastHeartbeatAt,
Status: a.status,
ServerConnectivity: a.serverConnectivity,
ScreenID: a.Config.ScreenID,
ServerBaseURL: a.Config.ServerBaseURL,
MQTTBroker: a.Config.MQTTBroker,
HeartbeatEvery: a.Config.HeartbeatEvery,
StartedAt: a.startedAt,
LastHeartbeatAt: a.lastHeartbeatAt,
}
}
@ -170,9 +180,15 @@ func (a *App) reportStatus(ctx context.Context) {
LastHeartbeatAt: snapshot.LastHeartbeatAt,
})
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)
return
}
a.mu.Lock()
a.serverConnectivity = ConnectivityOnline
a.mu.Unlock()
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 {
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) {
@ -195,4 +199,40 @@ func TestAppRunReportsStatusWithoutStoppingOnReporterError(t *testing.T) {
if !strings.Contains(logs, "event=status_report_failed") {
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
}