Files
wehub-resource-sync 6d978fe483
Build / Frontend Build (push) Has been cancelled
Build / Backend Build (push) Has been cancelled
Build / Docker Build (push) Has been cancelled
Build / Kubernetes Deploy Smoke Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:24 +08:00

191 lines
6.8 KiB
Go

package services
import (
"encoding/json"
"fmt"
"strings"
"time"
"clawreef/internal/models"
"clawreef/internal/repository"
)
type AgentStateReportRequest struct {
AgentID string `json:"agent_id" binding:"required"`
ReportedAt *time.Time `json:"reported_at,omitempty"`
Runtime AgentRuntimePayload `json:"runtime"`
SystemInfo map[string]interface{} `json:"system_info"`
Health map[string]interface{} `json:"health"`
}
type AgentRuntimePayload struct {
OpenClawStatus string `json:"openclaw_status"`
OpenClawPID *int `json:"openclaw_pid,omitempty"`
OpenClawVersion string `json:"openclaw_version"`
CurrentConfigRevisionID *int `json:"current_config_revision_id,omitempty"`
}
type InstanceRuntimeStatusPayload struct {
InstanceID int `json:"instance_id"`
InfraStatus string `json:"infra_status"`
AgentStatus string `json:"agent_status"`
OpenClawStatus string `json:"openclaw_status"`
OpenClawPID *int `json:"openclaw_pid,omitempty"`
OpenClawVersion *string `json:"openclaw_version,omitempty"`
CurrentConfigRevisionID *int `json:"current_config_revision_id,omitempty"`
DesiredConfigRevisionID *int `json:"desired_config_revision_id,omitempty"`
SystemInfo map[string]interface{} `json:"system_info,omitempty"`
Health map[string]interface{} `json:"health,omitempty"`
Summary map[string]interface{} `json:"summary,omitempty"`
LastReportedAt *time.Time `json:"last_reported_at,omitempty"`
}
type InstanceRuntimeStatusService interface {
Report(session *AgentSession, req AgentStateReportRequest, clientIP string) error
GetByInstanceID(instanceID int) (*InstanceRuntimeStatusPayload, error)
UpsertInfraStatus(instanceID int, infraStatus string) error
}
type instanceRuntimeStatusService struct {
runtimeRepo repository.InstanceRuntimeStatusRepository
agentRepo repository.InstanceAgentRepository
desiredStateRepo repository.InstanceDesiredStateRepository
}
func NewInstanceRuntimeStatusService(runtimeRepo repository.InstanceRuntimeStatusRepository, agentRepo repository.InstanceAgentRepository, desiredStateRepo repository.InstanceDesiredStateRepository) InstanceRuntimeStatusService {
return &instanceRuntimeStatusService{
runtimeRepo: runtimeRepo,
agentRepo: agentRepo,
desiredStateRepo: desiredStateRepo,
}
}
func (s *instanceRuntimeStatusService) Report(session *AgentSession, req AgentStateReportRequest, clientIP string) error {
if session == nil || session.Agent == nil || session.Instance == nil {
return fmt.Errorf("agent session is required")
}
if strings.TrimSpace(req.AgentID) != session.Agent.AgentID {
return fmt.Errorf("agent id does not match session")
}
status, err := s.getOrCreate(session.Instance.ID)
if err != nil {
return err
}
now := time.Now().UTC()
reportedAt := req.ReportedAt
if reportedAt == nil || reportedAt.IsZero() {
reportedAt = &now
}
status.AgentStatus = agentStatusOnline
if strings.TrimSpace(req.Runtime.OpenClawStatus) != "" {
status.OpenClawStatus = strings.TrimSpace(req.Runtime.OpenClawStatus)
}
status.OpenClawPID = req.Runtime.OpenClawPID
if strings.TrimSpace(req.Runtime.OpenClawVersion) != "" {
version := strings.TrimSpace(req.Runtime.OpenClawVersion)
status.OpenClawVersion = &version
}
status.CurrentConfigRevisionID = req.Runtime.CurrentConfigRevisionID
status.LastReportedAt = reportedAt
systemInfoJSON, err := marshalOptionalJSON(req.SystemInfo)
if err != nil {
return fmt.Errorf("failed to encode system info: %w", err)
}
healthJSON, err := marshalOptionalJSON(req.Health)
if err != nil {
return fmt.Errorf("failed to encode health info: %w", err)
}
status.SystemInfoJSON = systemInfoJSON
status.HealthJSON = healthJSON
if err := s.runtimeRepo.Update(status); err != nil {
return err
}
session.Agent.Status = agentStatusOnline
session.Agent.LastHeartbeatAt = reportedAt
session.Agent.LastReportedAt = reportedAt
session.Agent.LastSeenIP = optionalString(strings.TrimSpace(clientIP))
if err := s.agentRepo.Update(session.Agent); err != nil {
return err
}
return nil
}
func (s *instanceRuntimeStatusService) GetByInstanceID(instanceID int) (*InstanceRuntimeStatusPayload, error) {
status, err := s.runtimeRepo.GetByInstanceID(instanceID)
if err != nil {
return nil, err
}
if status == nil {
return nil, nil
}
payload := &InstanceRuntimeStatusPayload{
InstanceID: status.InstanceID,
InfraStatus: status.InfraStatus,
AgentStatus: status.AgentStatus,
OpenClawStatus: status.OpenClawStatus,
OpenClawPID: status.OpenClawPID,
OpenClawVersion: status.OpenClawVersion,
CurrentConfigRevisionID: status.CurrentConfigRevisionID,
DesiredConfigRevisionID: status.DesiredConfigRevisionID,
LastReportedAt: status.LastReportedAt,
}
if status.SystemInfoJSON != nil && strings.TrimSpace(*status.SystemInfoJSON) != "" {
if err := json.Unmarshal([]byte(*status.SystemInfoJSON), &payload.SystemInfo); err != nil {
return nil, fmt.Errorf("failed to decode system info: %w", err)
}
}
if status.HealthJSON != nil && strings.TrimSpace(*status.HealthJSON) != "" {
if err := json.Unmarshal([]byte(*status.HealthJSON), &payload.Health); err != nil {
return nil, fmt.Errorf("failed to decode health info: %w", err)
}
}
if status.SummaryJSON != nil && strings.TrimSpace(*status.SummaryJSON) != "" {
if err := json.Unmarshal([]byte(*status.SummaryJSON), &payload.Summary); err != nil {
return nil, fmt.Errorf("failed to decode runtime summary: %w", err)
}
}
return payload, nil
}
func (s *instanceRuntimeStatusService) UpsertInfraStatus(instanceID int, infraStatus string) error {
status, err := s.getOrCreate(instanceID)
if err != nil {
return err
}
status.InfraStatus = infraStatus
desiredState, err := s.desiredStateRepo.GetByInstanceID(instanceID)
if err == nil && desiredState != nil {
status.DesiredConfigRevisionID = desiredState.DesiredConfigRevisionID
}
return s.runtimeRepo.Update(status)
}
func (s *instanceRuntimeStatusService) getOrCreate(instanceID int) (*models.InstanceRuntimeStatus, error) {
status, err := s.runtimeRepo.GetByInstanceID(instanceID)
if err != nil {
return nil, err
}
if status != nil {
return status, nil
}
now := time.Now().UTC()
status = &models.InstanceRuntimeStatus{
InstanceID: instanceID,
InfraStatus: "creating",
AgentStatus: agentStatusOffline,
OpenClawStatus: openClawStatusUnknown,
CreatedAt: now,
UpdatedAt: now,
}
if err := s.runtimeRepo.Create(status); err != nil {
return nil, err
}
return status, nil
}