yuan-lab-llm--clawmanager
41d8d6a629
* chore: ignore local agency and team snapshots * feat: add team agent profile templates * KANBAN * template * prototype * Fix tenant runtime pool deployment * Fix local team profiles runtime override * Update local team profiles testing guide * Adapt local team profiles test deployment * Patch tenant NFS server to ClusterIP * Use NFS export root for workspace mounts * Tighten team task completion projection * Fix team event body map comparison * before modification * temp_peer1 * leadermodel * team_fix * fix(team): harden workflow completion and workspace handling * fix(team): harden redis workflow protocol
404 行
12 KiB
Go
404 行
12 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"clawreef/internal/models"
|
|
"clawreef/internal/repository"
|
|
)
|
|
|
|
var orderedSystemImageTypes = []string{
|
|
"openclaw",
|
|
"ubuntu",
|
|
"webtop",
|
|
"hermes",
|
|
"debian",
|
|
"centos",
|
|
"custom",
|
|
}
|
|
|
|
var supportedSystemImageTypes = map[string]string{
|
|
"openclaw": "OpenClaw Pro",
|
|
"ubuntu": "Ubuntu Desktop",
|
|
"webtop": "Webtop Desktop",
|
|
"hermes": "Hermes Pro",
|
|
"debian": "Debian Desktop",
|
|
"centos": "CentOS Desktop",
|
|
"custom": "Custom Image",
|
|
}
|
|
|
|
var defaultSystemImageSettings = map[string]string{
|
|
"openclaw": "ghcr.io/yuan-lab-llm/agentsruntime/openclaw:latest",
|
|
"ubuntu": "lscr.io/linuxserver/webtop:ubuntu-xfce",
|
|
"webtop": "lscr.io/linuxserver/webtop:ubuntu-xfce",
|
|
"hermes": "ghcr.io/yuan-lab-llm/agentsruntime/hermes:latest",
|
|
"debian": "docker.io/clawreef/debian-desktop:12",
|
|
"centos": "docker.io/clawreef/centos-desktop:9",
|
|
"custom": "registry.example.com/your-custom-image:latest",
|
|
}
|
|
|
|
var defaultGatewaySystemImageSettings = map[string]string{
|
|
"openclaw": "ghcr.io/yuan-lab-llm/agentsruntime/openclaw-lite:latest",
|
|
"ubuntu": "ubuntu:22.04",
|
|
"webtop": "ubuntu:22.04",
|
|
"hermes": "ghcr.io/yuan-lab-llm/agentsruntime/hermes-lite:latest",
|
|
"debian": "debian:12",
|
|
"centos": "quay.io/centos/centos:stream9",
|
|
"custom": "registry.example.com/your-custom-shell-image:latest",
|
|
}
|
|
|
|
var defaultEnabledSystemImageTypes = map[string]bool{
|
|
"openclaw": true,
|
|
"ubuntu": true,
|
|
"hermes": true,
|
|
}
|
|
|
|
var defaultEnabledGatewaySystemImageTypes = map[string]bool{
|
|
"openclaw": true,
|
|
"hermes": true,
|
|
}
|
|
|
|
// RuntimeImageConfig is the runtime card selected for an instance type.
|
|
type RuntimeImageConfig struct {
|
|
Image string
|
|
RuntimeType string
|
|
}
|
|
|
|
// RuntimeImageSettingsProvider exposes runtime image lookup for instance types.
|
|
type RuntimeImageSettingsProvider interface {
|
|
GetRuntimeImage(instanceType string) (RuntimeImageConfig, bool)
|
|
GetRuntimeImageForImage(instanceType, image string) (RuntimeImageConfig, bool)
|
|
}
|
|
|
|
var runtimeImageSettingsProvider RuntimeImageSettingsProvider
|
|
|
|
// SetRuntimeImageSettingsProvider configures the global runtime image provider used by runtime resolution.
|
|
func SetRuntimeImageSettingsProvider(provider RuntimeImageSettingsProvider) {
|
|
runtimeImageSettingsProvider = provider
|
|
}
|
|
|
|
type SystemImageSettingService interface {
|
|
List() ([]models.SystemImageSetting, error)
|
|
Save(setting *models.SystemImageSetting) (*models.SystemImageSetting, error)
|
|
DeleteByID(id int) error
|
|
DisableType(instanceType string) error
|
|
GetRuntimeImage(instanceType string) (RuntimeImageConfig, bool)
|
|
GetRuntimeImageForImage(instanceType, image string) (RuntimeImageConfig, bool)
|
|
}
|
|
|
|
type systemImageSettingService struct {
|
|
repo repository.SystemImageSettingRepository
|
|
}
|
|
|
|
// NewSystemImageSettingService creates a new system image setting service.
|
|
func NewSystemImageSettingService(repo repository.SystemImageSettingRepository) SystemImageSettingService {
|
|
return &systemImageSettingService{repo: repo}
|
|
}
|
|
|
|
func (s *systemImageSettingService) List() ([]models.SystemImageSetting, error) {
|
|
stored, err := s.repo.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
byType := make(map[string][]models.SystemImageSetting, len(orderedSystemImageTypes))
|
|
for _, item := range stored {
|
|
normalizedType := strings.TrimSpace(strings.ToLower(item.InstanceType))
|
|
item.InstanceType = normalizedType
|
|
item.RuntimeType = normalizeSystemImageRuntimeType(item.RuntimeType)
|
|
if strings.TrimSpace(item.DisplayName) == "" {
|
|
item.DisplayName = displayNameForSystemImageType(normalizedType)
|
|
}
|
|
byType[normalizedType] = append(byType[normalizedType], item)
|
|
}
|
|
|
|
settings := make([]models.SystemImageSetting, 0, len(stored)+len(orderedSystemImageTypes))
|
|
for _, instanceType := range orderedSystemImageTypes {
|
|
items := byType[instanceType]
|
|
if len(items) == 0 {
|
|
settings = append(settings, defaultSystemImagePresetsForType(instanceType)...)
|
|
continue
|
|
}
|
|
|
|
settings = append(settings, enabledSystemImageSettingsForType(instanceType, items)...)
|
|
delete(byType, instanceType)
|
|
}
|
|
|
|
for instanceType, items := range byType {
|
|
settings = append(settings, enabledSystemImageSettingsForType(instanceType, items)...)
|
|
}
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
func (s *systemImageSettingService) Save(setting *models.SystemImageSetting) (*models.SystemImageSetting, error) {
|
|
normalizedType := strings.TrimSpace(strings.ToLower(setting.InstanceType))
|
|
if _, ok := supportedSystemImageTypes[normalizedType]; !ok {
|
|
return nil, errors.New("unsupported instance type")
|
|
}
|
|
|
|
runtimeType, err := validateSystemImageRuntimeType(setting.RuntimeType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
image := strings.TrimSpace(setting.Image)
|
|
if image == "" {
|
|
return nil, errors.New("image is required")
|
|
}
|
|
|
|
setting.InstanceType = normalizedType
|
|
setting.RuntimeType = runtimeType
|
|
setting.Image = image
|
|
setting.DisplayName = strings.TrimSpace(setting.DisplayName)
|
|
if setting.DisplayName == "" {
|
|
setting.DisplayName = displayNameForSystemImagePreset(normalizedType, runtimeType)
|
|
}
|
|
setting.IsEnabled = true
|
|
|
|
if err := s.repo.Save(setting); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setting, nil
|
|
}
|
|
|
|
func (s *systemImageSettingService) DeleteByID(id int) error {
|
|
if id <= 0 {
|
|
return errors.New("invalid image setting id")
|
|
}
|
|
|
|
existing, err := s.repo.GetByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := s.repo.DeleteByID(id); err != nil {
|
|
return err
|
|
}
|
|
|
|
remaining, err := s.repo.ListByInstanceType(existing.InstanceType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(remaining) > 0 || !isSupportedSystemImageType(existing.InstanceType) {
|
|
return nil
|
|
}
|
|
|
|
return s.disableTypeWithFallback(existing.InstanceType)
|
|
}
|
|
|
|
func (s *systemImageSettingService) DisableType(instanceType string) error {
|
|
normalizedType := strings.TrimSpace(strings.ToLower(instanceType))
|
|
if !isSupportedSystemImageType(normalizedType) {
|
|
return errors.New("unsupported instance type")
|
|
}
|
|
|
|
return s.disableTypeWithFallback(normalizedType)
|
|
}
|
|
|
|
func (s *systemImageSettingService) disableTypeWithFallback(instanceType string) error {
|
|
if err := s.repo.DeleteByInstanceType(instanceType); err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.repo.Save(&models.SystemImageSetting{
|
|
InstanceType: instanceType,
|
|
RuntimeType: "desktop",
|
|
DisplayName: displayNameForSystemImagePreset(instanceType, "desktop"),
|
|
Image: defaultSystemImageSettings[instanceType],
|
|
IsEnabled: false,
|
|
})
|
|
}
|
|
|
|
func (s *systemImageSettingService) GetRuntimeImage(instanceType string) (RuntimeImageConfig, bool) {
|
|
normalizedType := strings.TrimSpace(strings.ToLower(instanceType))
|
|
items, err := s.repo.ListByInstanceType(normalizedType)
|
|
if err != nil {
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
if len(items) == 0 {
|
|
for _, item := range defaultSystemImagePresetsForType(normalizedType) {
|
|
image := strings.TrimSpace(item.Image)
|
|
if item.IsEnabled && image != "" {
|
|
return RuntimeImageConfig{Image: image, RuntimeType: item.RuntimeType}, true
|
|
}
|
|
}
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
for _, item := range enabledSystemImageSettingsForType(normalizedType, items) {
|
|
image := strings.TrimSpace(item.Image)
|
|
if image != "" {
|
|
return RuntimeImageConfig{
|
|
Image: image,
|
|
RuntimeType: normalizeSystemImageRuntimeType(item.RuntimeType),
|
|
}, true
|
|
}
|
|
}
|
|
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
func (s *systemImageSettingService) GetRuntimeImageForImage(instanceType, image string) (RuntimeImageConfig, bool) {
|
|
normalizedType := strings.TrimSpace(strings.ToLower(instanceType))
|
|
normalizedImage := strings.TrimSpace(image)
|
|
if normalizedType == "" || normalizedImage == "" {
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
items, err := s.repo.ListByInstanceType(normalizedType)
|
|
if err != nil {
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
if len(items) == 0 {
|
|
for _, item := range defaultSystemImagePresetsForType(normalizedType) {
|
|
defaultImage := strings.TrimSpace(item.Image)
|
|
if item.IsEnabled && defaultImage == normalizedImage {
|
|
return RuntimeImageConfig{Image: defaultImage, RuntimeType: item.RuntimeType}, true
|
|
}
|
|
}
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
for _, item := range enabledSystemImageSettingsForType(normalizedType, items) {
|
|
if strings.TrimSpace(item.Image) == normalizedImage {
|
|
return RuntimeImageConfig{
|
|
Image: normalizedImage,
|
|
RuntimeType: normalizeSystemImageRuntimeType(item.RuntimeType),
|
|
}, true
|
|
}
|
|
}
|
|
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
|
|
func runtimeImageOverride(instanceType string) (RuntimeImageConfig, bool) {
|
|
if runtimeImageSettingsProvider == nil {
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
return runtimeImageSettingsProvider.GetRuntimeImage(instanceType)
|
|
}
|
|
|
|
func runtimeImageOverrideForImage(instanceType, image string) (RuntimeImageConfig, bool) {
|
|
if runtimeImageSettingsProvider == nil {
|
|
return RuntimeImageConfig{}, false
|
|
}
|
|
return runtimeImageSettingsProvider.GetRuntimeImageForImage(instanceType, image)
|
|
}
|
|
|
|
func displayNameForSystemImageType(instanceType string) string {
|
|
if name, ok := supportedSystemImageTypes[instanceType]; ok {
|
|
return name
|
|
}
|
|
return fmt.Sprintf("%s Image", instanceType)
|
|
}
|
|
|
|
func displayNameForSystemImagePreset(instanceType, runtimeType string) string {
|
|
normalizedRuntimeType := normalizeSystemImageRuntimeType(runtimeType)
|
|
if instanceType == "openclaw" {
|
|
if normalizedRuntimeType == "gateway" {
|
|
return "OpenClaw Lite"
|
|
}
|
|
return "OpenClaw Pro"
|
|
}
|
|
if instanceType == "hermes" {
|
|
if normalizedRuntimeType == "gateway" {
|
|
return "Hermes Lite"
|
|
}
|
|
return "Hermes Pro"
|
|
}
|
|
return displayNameForSystemImageType(instanceType)
|
|
}
|
|
|
|
func defaultSystemImagePresetsForType(instanceType string) []models.SystemImageSetting {
|
|
settings := []models.SystemImageSetting{{
|
|
InstanceType: instanceType,
|
|
RuntimeType: "desktop",
|
|
DisplayName: displayNameForSystemImagePreset(instanceType, "desktop"),
|
|
Image: defaultSystemImageSettings[instanceType],
|
|
IsEnabled: defaultEnabledSystemImageTypes[instanceType],
|
|
}}
|
|
|
|
if image := strings.TrimSpace(defaultGatewaySystemImageSettings[instanceType]); image != "" {
|
|
if defaultEnabledGatewaySystemImageTypes[instanceType] {
|
|
settings = append(settings, models.SystemImageSetting{
|
|
InstanceType: instanceType,
|
|
RuntimeType: "gateway",
|
|
DisplayName: displayNameForSystemImagePreset(instanceType, "gateway"),
|
|
Image: image,
|
|
IsEnabled: true,
|
|
})
|
|
}
|
|
}
|
|
|
|
return settings
|
|
}
|
|
|
|
func enabledSystemImageSettingsForType(instanceType string, stored []models.SystemImageSetting) []models.SystemImageSetting {
|
|
if len(stored) == 0 {
|
|
return defaultSystemImagePresetsForType(instanceType)
|
|
}
|
|
|
|
hasEnabled := false
|
|
runtimeTypesWithRows := map[string]bool{}
|
|
result := make([]models.SystemImageSetting, 0, len(stored)+2)
|
|
for _, item := range stored {
|
|
item.RuntimeType = normalizeSystemImageRuntimeType(item.RuntimeType)
|
|
runtimeTypesWithRows[item.RuntimeType] = true
|
|
if strings.TrimSpace(item.DisplayName) == "" {
|
|
item.DisplayName = displayNameForSystemImagePreset(instanceType, item.RuntimeType)
|
|
}
|
|
if item.IsEnabled {
|
|
hasEnabled = true
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
|
|
if !hasEnabled {
|
|
return result
|
|
}
|
|
|
|
for _, preset := range defaultSystemImagePresetsForType(instanceType) {
|
|
if runtimeTypesWithRows[preset.RuntimeType] || !preset.IsEnabled || strings.TrimSpace(preset.Image) == "" {
|
|
continue
|
|
}
|
|
result = append(result, preset)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func isSupportedSystemImageType(instanceType string) bool {
|
|
_, ok := supportedSystemImageTypes[instanceType]
|
|
return ok
|
|
}
|
|
|
|
func normalizeSystemImageRuntimeType(runtimeType string) string {
|
|
normalized := strings.TrimSpace(strings.ToLower(runtimeType))
|
|
if normalized == "gateway" || normalized == "shell" {
|
|
return "gateway"
|
|
}
|
|
return "desktop"
|
|
}
|
|
|
|
func validateSystemImageRuntimeType(runtimeType string) (string, error) {
|
|
normalized := strings.TrimSpace(strings.ToLower(runtimeType))
|
|
if normalized == "" {
|
|
return "desktop", nil
|
|
}
|
|
if normalized == "shell" {
|
|
return "gateway", nil
|
|
}
|
|
if normalized != "desktop" && normalized != "gateway" {
|
|
return "", errors.New("unsupported runtime type")
|
|
}
|
|
return normalized, nil
|
|
}
|