feat: agent jobs panel (#7390)

* feat(agent): agent jobs

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Multiple webhooks, simplify

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Do not use cron with seconds

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Create separate pages for details

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Detect if no models have MCP configuration, show wizard

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Make services test to run

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2025-11-28 23:05:39 +01:00
committed by GitHub
parent 4b5977f535
commit 53e5b2d6be
25 changed files with 4308 additions and 19 deletions

View File

@@ -0,0 +1,43 @@
package application
import (
"time"
"github.com/mudler/LocalAI/core/services"
"github.com/rs/zerolog/log"
)
// RestartAgentJobService restarts the agent job service with current ApplicationConfig settings
func (a *Application) RestartAgentJobService() error {
a.agentJobMutex.Lock()
defer a.agentJobMutex.Unlock()
// Stop existing service if running
if a.agentJobService != nil {
if err := a.agentJobService.Stop(); err != nil {
log.Warn().Err(err).Msg("Error stopping agent job service")
}
// Wait a bit for shutdown to complete
time.Sleep(200 * time.Millisecond)
}
// Create new service instance
agentJobService := services.NewAgentJobService(
a.ApplicationConfig(),
a.ModelLoader(),
a.ModelConfigLoader(),
a.TemplatesEvaluator(),
)
// Start the service
err := agentJobService.Start(a.ApplicationConfig().Context)
if err != nil {
log.Error().Err(err).Msg("Failed to start agent job service")
return err
}
a.agentJobService = agentJobService
log.Info().Msg("Agent job service restarted")
return nil
}

View File

@@ -17,11 +17,13 @@ type Application struct {
startupConfig *config.ApplicationConfig // Stores original config from env vars (before file loading)
templatesEvaluator *templates.Evaluator
galleryService *services.GalleryService
agentJobService *services.AgentJobService
watchdogMutex sync.Mutex
watchdogStop chan bool
p2pMutex sync.Mutex
p2pCtx context.Context
p2pCancel context.CancelFunc
agentJobMutex sync.Mutex
}
func newApplication(appConfig *config.ApplicationConfig) *Application {
@@ -53,6 +55,10 @@ func (a *Application) GalleryService() *services.GalleryService {
return a.galleryService
}
func (a *Application) AgentJobService() *services.AgentJobService {
return a.agentJobService
}
// StartupConfig returns the original startup configuration (from env vars, before file loading)
func (a *Application) StartupConfig() *config.ApplicationConfig {
return a.startupConfig
@@ -67,5 +73,20 @@ func (a *Application) start() error {
a.galleryService = galleryService
// Initialize agent job service
agentJobService := services.NewAgentJobService(
a.ApplicationConfig(),
a.ModelLoader(),
a.ModelConfigLoader(),
a.TemplatesEvaluator(),
)
err = agentJobService.Start(a.ApplicationConfig().Context)
if err != nil {
return err
}
a.agentJobService = agentJobService
return nil
}

View File

@@ -43,6 +43,8 @@ func newConfigFileHandler(appConfig *config.ApplicationConfig) configFileHandler
if err != nil {
log.Error().Err(err).Str("file", "runtime_settings.json").Msg("unable to register config file handler")
}
// Note: agent_tasks.json and agent_jobs.json are handled by AgentJobService directly
// The service watches and reloads these files internally
return c
}
@@ -206,6 +208,7 @@ type runtimeSettings struct {
AutoloadGalleries *bool `json:"autoload_galleries,omitempty"`
AutoloadBackendGalleries *bool `json:"autoload_backend_galleries,omitempty"`
ApiKeys *[]string `json:"api_keys,omitempty"`
AgentJobRetentionDays *int `json:"agent_job_retention_days,omitempty"`
}
func readRuntimeSettingsJson(startupAppConfig config.ApplicationConfig) fileHandler {
@@ -234,6 +237,7 @@ func readRuntimeSettingsJson(startupAppConfig config.ApplicationConfig) fileHand
envFederated := appConfig.Federated == startupAppConfig.Federated
envAutoloadGalleries := appConfig.AutoloadGalleries == startupAppConfig.AutoloadGalleries
envAutoloadBackendGalleries := appConfig.AutoloadBackendGalleries == startupAppConfig.AutoloadBackendGalleries
envAgentJobRetentionDays := appConfig.AgentJobRetentionDays == startupAppConfig.AgentJobRetentionDays
if len(fileContent) > 0 {
var settings runtimeSettings
@@ -328,6 +332,9 @@ func readRuntimeSettingsJson(startupAppConfig config.ApplicationConfig) fileHand
// Replace all runtime keys with what's in runtime_settings.json
appConfig.ApiKeys = append(envKeys, runtimeKeys...)
}
if settings.AgentJobRetentionDays != nil && !envAgentJobRetentionDays {
appConfig.AgentJobRetentionDays = *settings.AgentJobRetentionDays
}
// If watchdog is enabled via file but not via env, ensure WatchDog flag is set
if !envWatchdogIdle && !envWatchdogBusy {

View File

@@ -226,6 +226,7 @@ func loadRuntimeSettingsFromFile(options *config.ApplicationConfig) {
WatchdogBusyTimeout *string `json:"watchdog_busy_timeout,omitempty"`
SingleBackend *bool `json:"single_backend,omitempty"`
ParallelBackendRequests *bool `json:"parallel_backend_requests,omitempty"`
AgentJobRetentionDays *int `json:"agent_job_retention_days,omitempty"`
}
if err := json.Unmarshal(fileContent, &settings); err != nil {
@@ -289,6 +290,12 @@ func loadRuntimeSettingsFromFile(options *config.ApplicationConfig) {
options.ParallelBackendRequests = *settings.ParallelBackendRequests
}
}
if settings.AgentJobRetentionDays != nil {
// Only apply if current value is default (0), suggesting it wasn't set from env var
if options.AgentJobRetentionDays == 0 {
options.AgentJobRetentionDays = *settings.AgentJobRetentionDays
}
}
if !options.WatchDogIdle && !options.WatchDogBusy {
if settings.WatchdogEnabled != nil && *settings.WatchdogEnabled {
options.WatchDog = true