feat(api): per-agent unified control + clear_memory + delete_cache
- Manager: RegisterUnifiedAgent/UnregisterUnifiedAgent/StopUnifiedAgent/
IsUnifiedAgentRunning/UptimeSeconds — cancela goroutines individuales sin
matar el launcher
- Manager: UptimeSeconds en AgentStatus via startedAt map
- api/server: AgentController interface + WithController/WithDataDir builders
+ rutas POST /agents/{id}/clear_memory y /agents/{id}/delete_cache
- api/handlers: handleStartAgent/Stop/Restart delegan a controller en modo
unified; Messages24h enriquecido via queryMessages24h (cache 30s)
- api/handlers: handleClearMemory — para la goroutine, borra messages+facts de
memory.db, responde {status,messages_deleted,facts_deleted}
- api/handlers: handleDeleteCache — para la goroutine, elimina crypto/ y cache/,
responde {status,paths_deleted}
- launcher/registry: launchGoroutine extrae goroutine con contexto per-agente;
deps.procMgr hookea RegisterUnified; startAgent permite relanzar via reload
- launcher/main: agentController implementa api.AgentController sobre registry;
mgr compartido entre API y registry; WithController+WithDataDir cableados
Co-Authored-By: fn-orquestador <noreply@fn-registry>
This commit is contained in:
+94
-10
@@ -4,12 +4,14 @@ package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -29,9 +31,10 @@ type AgentInfo struct {
|
||||
// AgentStatus combines agent metadata with runtime state.
|
||||
type AgentStatus struct {
|
||||
AgentInfo
|
||||
Running bool
|
||||
PID int
|
||||
Instances int
|
||||
Running bool
|
||||
PID int
|
||||
Instances int
|
||||
UptimeSeconds int64 // seconds since agent goroutine started (unified mode) or 0
|
||||
}
|
||||
|
||||
// ProcessStats holds resource usage for a running process.
|
||||
@@ -91,11 +94,25 @@ type Manager struct {
|
||||
binPath string
|
||||
envFile string // path to .env file for child processes
|
||||
prober processProber
|
||||
|
||||
// unifiedMode tracks per-agent goroutine cancel functions and start times
|
||||
// when the unified launcher is running (all agents as goroutines).
|
||||
unifiedMu sync.RWMutex
|
||||
unifiedCancels map[string]context.CancelFunc
|
||||
startedAt map[string]time.Time
|
||||
}
|
||||
|
||||
// NewManager creates a Manager. binPath can be empty for auto-detection.
|
||||
func NewManager(runDir, agentsGlob, binPath string) *Manager {
|
||||
return &Manager{runDir: runDir, agentsGlob: agentsGlob, binPath: binPath, envFile: ".env", prober: osProber{}}
|
||||
return &Manager{
|
||||
runDir: runDir,
|
||||
agentsGlob: agentsGlob,
|
||||
binPath: binPath,
|
||||
envFile: ".env",
|
||||
prober: osProber{},
|
||||
unifiedCancels: make(map[string]context.CancelFunc),
|
||||
startedAt: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// Scan discovers all agents from config files.
|
||||
@@ -484,8 +501,63 @@ func (m *Manager) UnifiedLogTail(lines int) ([]string, error) {
|
||||
return m.LogTail(unifiedID, lines)
|
||||
}
|
||||
|
||||
// ── Per-agent unified control ─────────────────────────────────────────────
|
||||
|
||||
// RegisterUnifiedAgent registers a cancel function and start time for an agent
|
||||
// goroutine running inside the unified launcher. Called by the launcher runtime.
|
||||
func (m *Manager) RegisterUnifiedAgent(id string, cancel context.CancelFunc) {
|
||||
m.unifiedMu.Lock()
|
||||
defer m.unifiedMu.Unlock()
|
||||
m.unifiedCancels[id] = cancel
|
||||
m.startedAt[id] = time.Now()
|
||||
}
|
||||
|
||||
// UnregisterUnifiedAgent removes the cancel function for an agent goroutine.
|
||||
// Called when the goroutine exits.
|
||||
func (m *Manager) UnregisterUnifiedAgent(id string) {
|
||||
m.unifiedMu.Lock()
|
||||
defer m.unifiedMu.Unlock()
|
||||
delete(m.unifiedCancels, id)
|
||||
delete(m.startedAt, id)
|
||||
}
|
||||
|
||||
// StopUnifiedAgent cancels the goroutine context for a specific agent without
|
||||
// stopping the launcher process. Returns error if agent is not registered.
|
||||
func (m *Manager) StopUnifiedAgent(id string) error {
|
||||
m.unifiedMu.RLock()
|
||||
cancel, ok := m.unifiedCancels[id]
|
||||
m.unifiedMu.RUnlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("agent %q is not registered in unified mode (not running)", id)
|
||||
}
|
||||
cancel()
|
||||
m.UnregisterUnifiedAgent(id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsUnifiedAgentRunning returns true if the agent goroutine is registered.
|
||||
func (m *Manager) IsUnifiedAgentRunning(id string) bool {
|
||||
m.unifiedMu.RLock()
|
||||
defer m.unifiedMu.RUnlock()
|
||||
_, ok := m.unifiedCancels[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
// UptimeSeconds returns how long an agent has been running since registration.
|
||||
// Returns 0 if the agent is not registered or not running.
|
||||
func (m *Manager) UptimeSeconds(id string) int64 {
|
||||
m.unifiedMu.RLock()
|
||||
defer m.unifiedMu.RUnlock()
|
||||
if t, ok := m.startedAt[id]; ok {
|
||||
return int64(time.Since(t).Seconds())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// StatusAllUnified returns status for all agents, deriving "running" from
|
||||
// whether the unified launcher is running + the agent is enabled.
|
||||
// whether the unified launcher is running + per-agent registration.
|
||||
// When per-agent cancel registration is available (via RegisterUnifiedAgent),
|
||||
// running reflects the individual goroutine state rather than launcher-wide enabled.
|
||||
func (m *Manager) StatusAllUnified() ([]AgentStatus, error) {
|
||||
agents, err := m.Scan()
|
||||
if err != nil {
|
||||
@@ -494,9 +566,20 @@ func (m *Manager) StatusAllUnified() ([]AgentStatus, error) {
|
||||
launcherRunning := m.IsUnifiedRunning()
|
||||
launcherPID := m.UnifiedPID()
|
||||
|
||||
m.unifiedMu.RLock()
|
||||
hasPerAgentTracking := len(m.unifiedCancels) > 0
|
||||
m.unifiedMu.RUnlock()
|
||||
|
||||
statuses := make([]AgentStatus, len(agents))
|
||||
for i, a := range agents {
|
||||
running := launcherRunning && a.Enabled
|
||||
var running bool
|
||||
if hasPerAgentTracking {
|
||||
// Per-agent goroutine tracking: check individual registration
|
||||
running = m.IsUnifiedAgentRunning(a.ID)
|
||||
} else {
|
||||
// Fallback: launcher running + agent enabled
|
||||
running = launcherRunning && a.Enabled
|
||||
}
|
||||
pid := 0
|
||||
instances := 0
|
||||
if running {
|
||||
@@ -504,10 +587,11 @@ func (m *Manager) StatusAllUnified() ([]AgentStatus, error) {
|
||||
instances = 1
|
||||
}
|
||||
statuses[i] = AgentStatus{
|
||||
AgentInfo: a,
|
||||
Running: running,
|
||||
PID: pid,
|
||||
Instances: instances,
|
||||
AgentInfo: a,
|
||||
Running: running,
|
||||
PID: pid,
|
||||
Instances: instances,
|
||||
UptimeSeconds: m.UptimeSeconds(a.ID),
|
||||
}
|
||||
}
|
||||
return statuses, nil
|
||||
|
||||
Reference in New Issue
Block a user