feat: enhance agent management with support for multiple instances and update UI accordingly
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
Permitir que los bots ejecuten herramientas reales (funciones Go) como respuesta a
|
||||
decisiones del LLM — patrón function calling / tool use.
|
||||
|
||||
## Estado: pendiente
|
||||
## Estado: COMPLETADO
|
||||
|
||||
---
|
||||
|
||||
|
||||
+10
-11
@@ -74,14 +74,15 @@ func listCmd(mgr *process.Manager) *cobra.Command {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("%-20s %-12s %-8s %s\n", "ID", "STATUS", "VERSION", "DESCRIPTION")
|
||||
fmt.Println(strings.Repeat("─", 72))
|
||||
fmt.Printf("%-20s %-14s %-8s %-4s %s\n", "ID", "STATUS", "VERSION", "INST", "DESCRIPTION")
|
||||
fmt.Println(strings.Repeat("─", 78))
|
||||
for _, s := range statuses {
|
||||
fmt.Printf("%-20s %-12s %-8s %s\n",
|
||||
fmt.Printf("%-20s %-14s %-8s %-4d %s\n",
|
||||
s.ID,
|
||||
statusLabel(s),
|
||||
s.Version,
|
||||
truncate(s.Desc, 36),
|
||||
s.Instances,
|
||||
truncate(s.Desc, 32),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
@@ -112,18 +113,13 @@ func startCmd(mgr *process.Manager, binPath *string) *cobra.Command {
|
||||
fmt.Printf("skip %-20s (disabled in config)\n", s.ID)
|
||||
continue
|
||||
}
|
||||
if s.Running {
|
||||
fmt.Printf("skip %-20s (already running, PID %d)\n", s.ID, s.PID)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := mgr.Start(s.AgentInfo); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fail %-20s %v\n", s.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("start %-20s PID %d log → %s\n",
|
||||
s.ID, mgr.ReadPID(s.ID), mgr.LogPath(s.ID))
|
||||
fmt.Printf("start %-20s PID %d (instances: %d) log → %s\n",
|
||||
s.ID, mgr.ReadPID(s.ID), mgr.InstanceCount(s.ID), mgr.LogPath(s.ID))
|
||||
started++
|
||||
}
|
||||
|
||||
@@ -244,6 +240,9 @@ func statusLabel(s process.AgentStatus) string {
|
||||
case !s.Enabled:
|
||||
return "disabled"
|
||||
case s.Running:
|
||||
if s.Instances > 1 {
|
||||
return fmt.Sprintf("● running(%d)", s.Instances)
|
||||
}
|
||||
return "● running"
|
||||
default:
|
||||
return "○ stopped"
|
||||
|
||||
+2
-14
@@ -16,14 +16,6 @@ start_agent() {
|
||||
local pid_f; pid_f="$(pid_file "$id")"
|
||||
local bin="$REPO_ROOT/bin/launcher"
|
||||
|
||||
# Check for duplicate instances already running
|
||||
local existing; existing="$(count_instances "$id")"
|
||||
if [[ "$existing" -gt 0 ]]; then
|
||||
warn "$id already has $existing instance(s) running (orphan processes?)"
|
||||
warn " Run ./dev-scripts/stop.sh $id first to clean up"
|
||||
return 1
|
||||
fi
|
||||
|
||||
info "Iniciando $id..."
|
||||
|
||||
# Build the binary first to avoid go run wrapper PID issues
|
||||
@@ -46,7 +38,8 @@ start_agent() {
|
||||
# Espera un momento y verifica que el proceso siga vivo
|
||||
sleep 1
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
ok "$id PID $pid → logs: $log"
|
||||
local inst; inst="$(count_instances "$id")"
|
||||
ok "$id PID $pid (instances: $inst) → logs: $log"
|
||||
else
|
||||
rm -f "$pid_f"
|
||||
fail "$id arrancó pero murió — revisa: tail -f $log"
|
||||
@@ -64,11 +57,6 @@ while IFS='|' read -r id version enabled desc cfg; do
|
||||
continue
|
||||
fi
|
||||
|
||||
if is_running "$id"; then
|
||||
warn "$id (ya corriendo, PID $(read_pid "$id"))"
|
||||
continue
|
||||
fi
|
||||
|
||||
start_agent "$id" "$cfg"
|
||||
((started++)) || true
|
||||
|
||||
|
||||
+4
-3
@@ -71,9 +71,10 @@ func ServerMenuOptions() []MenuOption {
|
||||
func AgentActionOptions(running bool) []MenuOption {
|
||||
if running {
|
||||
return []MenuOption{
|
||||
{Label: "Stop", Desc: "Detener el agente"},
|
||||
{Label: "Restart", Desc: "Reiniciar"},
|
||||
{Label: "Kill", Desc: "SIGKILL forzado"},
|
||||
{Label: "Start", Desc: "Iniciar otra instancia"},
|
||||
{Label: "Stop", Desc: "Detener todas las instancias"},
|
||||
{Label: "Restart", Desc: "Reiniciar (stop all + start)"},
|
||||
{Label: "Kill", Desc: "SIGKILL forzado a todas"},
|
||||
{Label: "Logs", Desc: "Ver log del agente"},
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -75,15 +75,15 @@ func viewAgentList(m Model) string {
|
||||
status = "disabled"
|
||||
} else if a.Running {
|
||||
icon = "●"
|
||||
status = fmt.Sprintf("running PID %d", a.PID)
|
||||
if a.Instances > 1 {
|
||||
status = fmt.Sprintf("running %d instances", a.Instances)
|
||||
} else {
|
||||
status = fmt.Sprintf("running PID %d", a.PID)
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString(fmt.Sprintf(" %s%s %-20s %-8s %s\n",
|
||||
cursor, icon, a.ID, a.Version, status))
|
||||
|
||||
if a.Instances > 1 {
|
||||
b.WriteString(fmt.Sprintf(" ⚠ WARNING: %d instances running!\n", a.Instances))
|
||||
}
|
||||
}
|
||||
|
||||
if m.StatusMsg != "" {
|
||||
@@ -104,7 +104,11 @@ func viewAgentActions(m Model) string {
|
||||
a := m.Selected
|
||||
icon := "○ stopped"
|
||||
if a.Running {
|
||||
icon = fmt.Sprintf("● running PID %d", a.PID)
|
||||
if a.Instances > 1 {
|
||||
icon = fmt.Sprintf("● running %d instances", a.Instances)
|
||||
} else {
|
||||
icon = fmt.Sprintf("● running PID %d", a.PID)
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString(fmt.Sprintf("\n %s %s\n", a.ID, icon))
|
||||
|
||||
+20
-12
@@ -29,8 +29,9 @@ type AgentInfo struct {
|
||||
// AgentStatus combines agent metadata with runtime state.
|
||||
type AgentStatus struct {
|
||||
AgentInfo
|
||||
Running bool
|
||||
PID int
|
||||
Running bool
|
||||
PID int
|
||||
Instances int
|
||||
}
|
||||
|
||||
// ProcessStats holds resource usage for a running process.
|
||||
@@ -82,9 +83,17 @@ func (m *Manager) Scan() ([]AgentInfo, error) {
|
||||
|
||||
// Status returns the runtime status for a single agent.
|
||||
func (m *Manager) Status(info AgentInfo) AgentStatus {
|
||||
pid := m.resolveRunningPID(info.ID)
|
||||
running := pid > 0
|
||||
return AgentStatus{AgentInfo: info, Running: running, PID: pid}
|
||||
pids := m.findProcessPIDs(info.ID)
|
||||
primary := 0
|
||||
if len(pids) > 0 {
|
||||
primary = pids[0]
|
||||
}
|
||||
return AgentStatus{
|
||||
AgentInfo: info,
|
||||
Running: len(pids) > 0,
|
||||
PID: primary,
|
||||
Instances: len(pids),
|
||||
}
|
||||
}
|
||||
|
||||
// StatusAll returns status for every discovered agent.
|
||||
@@ -101,13 +110,8 @@ func (m *Manager) StatusAll() ([]AgentStatus, error) {
|
||||
}
|
||||
|
||||
// Start launches an agent process in the background.
|
||||
// Multiple instances of the same agent are allowed.
|
||||
func (m *Manager) Start(info AgentInfo) error {
|
||||
// Check for orphan instances
|
||||
if existing := m.findProcessPIDs(info.ID); len(existing) > 0 {
|
||||
return fmt.Errorf("agent %q already has %d running instance(s) (PIDs: %v) — stop them first",
|
||||
info.ID, len(existing), existing)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(m.runDir, 0o755); err != nil {
|
||||
return fmt.Errorf("create run dir: %w", err)
|
||||
}
|
||||
@@ -233,7 +237,11 @@ func (m *Manager) Stats(id string) (ProcessStats, error) {
|
||||
if pid == 0 {
|
||||
return ProcessStats{}, fmt.Errorf("agent %q is not running", id)
|
||||
}
|
||||
return m.statsForPID(pid, id), nil
|
||||
}
|
||||
|
||||
// statsForPID gathers resource usage for a specific PID.
|
||||
func (m *Manager) statsForPID(pid int, id string) ProcessStats {
|
||||
s := ProcessStats{PID: pid}
|
||||
|
||||
// Uptime from /proc/<pid>/stat
|
||||
@@ -278,7 +286,7 @@ func (m *Manager) Stats(id string) (ProcessStats, error) {
|
||||
s.LogBytes = info.Size()
|
||||
}
|
||||
|
||||
return s, nil
|
||||
return s
|
||||
}
|
||||
|
||||
// LogTail returns the last N lines of an agent's log.
|
||||
|
||||
@@ -84,7 +84,7 @@ func (a *Adapter) loadAgents() tea.Cmd {
|
||||
Enabled: s.Enabled,
|
||||
Running: s.Running,
|
||||
PID: s.PID,
|
||||
Instances: a.mgr.InstanceCount(s.ID),
|
||||
Instances: s.Instances,
|
||||
}
|
||||
|
||||
if s.Running {
|
||||
|
||||
Reference in New Issue
Block a user