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