feat: update dashboard and process manager for unified launcher
Actualiza el dashboard TUI y el process manager para el modelo de launcher unificado donde todos los agentes corren en un solo proceso. Dashboard (pkg/tui): - model.go: campos de estado del launcher (PID, uptime, memory, CPU, log size) - model.go: ServerMenuOptions(running) contextual, AgentActionOptions(enabled) - messages.go: MsgAgentsLoaded incluye estado del launcher, MsgServerActionDone/MsgRebuildDone simplificados - update.go: intents nuevos (Enable/Disable agent, Start/Stop/Restart/Kill launcher) - view.go: vista de servidor muestra stats del launcher, agentes muestran enabled/disabled Shell adapter (shell/tui): - adapter.go: reescrito para usar métodos unificados (StartUnified, StopUnified, ToggleEnabled, StatusAllUnified, UnifiedStats, UnifiedLogTail) Process manager (shell/process): - manager.go: métodos StartUnified, StopUnified, KillUnified, IsUnifiedRunning, UnifiedPID, UnifiedStats, UnifiedLogTail, StatusAllUnified, ToggleEnabled Los agentes ya no se inician/detienen individualmente desde el dashboard. Se habilitan/deshabilitan en config y se reinicia el launcher para aplicar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+89
-233
@@ -30,32 +30,26 @@ func (a *Adapter) RunIntent(intent puretui.Intent) tea.Cmd {
|
||||
case puretui.IntentLoadAgents:
|
||||
return a.loadAgents()
|
||||
|
||||
case puretui.IntentStartAgent:
|
||||
return a.startAgent(intent.AgentID)
|
||||
case puretui.IntentEnableAgent:
|
||||
return a.enableAgent(intent.AgentID)
|
||||
|
||||
case puretui.IntentStopAgent:
|
||||
return a.stopAgent(intent.AgentID)
|
||||
|
||||
case puretui.IntentKillAgent:
|
||||
return a.killAgent(intent.AgentID)
|
||||
|
||||
case puretui.IntentRestartAgent:
|
||||
return a.restartAgent(intent.AgentID)
|
||||
case puretui.IntentDisableAgent:
|
||||
return a.disableAgent(intent.AgentID)
|
||||
|
||||
case puretui.IntentLoadLogs:
|
||||
return a.loadLogs(intent.AgentID)
|
||||
|
||||
case puretui.IntentStartAll:
|
||||
return a.startAll()
|
||||
case puretui.IntentStartLauncher:
|
||||
return a.startLauncher()
|
||||
|
||||
case puretui.IntentStopAll:
|
||||
return a.stopAll()
|
||||
case puretui.IntentStopLauncher:
|
||||
return a.stopLauncher()
|
||||
|
||||
case puretui.IntentRestartAll:
|
||||
return a.restartAll()
|
||||
case puretui.IntentRestartLauncher:
|
||||
return a.restartLauncher()
|
||||
|
||||
case puretui.IntentKillAll:
|
||||
return a.killAll()
|
||||
case puretui.IntentKillLauncher:
|
||||
return a.killLauncher()
|
||||
|
||||
case puretui.IntentRebuildRestart:
|
||||
return a.rebuildRestart()
|
||||
@@ -73,287 +67,149 @@ func (a *Adapter) RunIntent(intent puretui.Intent) tea.Cmd {
|
||||
|
||||
func (a *Adapter) loadAgents() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
statuses, err := a.mgr.StatusAll()
|
||||
statuses, err := a.mgr.StatusAllUnified()
|
||||
if err != nil {
|
||||
return puretui.MsgAgentsLoaded{}
|
||||
}
|
||||
|
||||
views := make([]puretui.AgentView, len(statuses))
|
||||
for i, s := range statuses {
|
||||
v := puretui.AgentView{
|
||||
ID: s.ID,
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Desc: s.Desc,
|
||||
Enabled: s.Enabled,
|
||||
Running: s.Running,
|
||||
PID: s.PID,
|
||||
Instances: s.Instances,
|
||||
views[i] = puretui.AgentView{
|
||||
ID: s.ID,
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Desc: s.Desc,
|
||||
Enabled: s.Enabled,
|
||||
Running: s.Running,
|
||||
PID: s.PID,
|
||||
}
|
||||
|
||||
if s.Running {
|
||||
if stats, err := a.mgr.Stats(s.ID); err == nil {
|
||||
v.Uptime = formatUptime(stats.UptimeSecs)
|
||||
v.Memory = formatBytes(stats.MemRSSKB * 1024)
|
||||
v.CPU = fmt.Sprintf("%.1f%%", stats.CPUPct)
|
||||
v.LogSize = formatBytes(stats.LogBytes)
|
||||
}
|
||||
}
|
||||
|
||||
views[i] = v
|
||||
}
|
||||
|
||||
return puretui.MsgAgentsLoaded{Agents: views}
|
||||
msg := puretui.MsgAgentsLoaded{
|
||||
Agents: views,
|
||||
LauncherRunning: a.mgr.IsUnifiedRunning(),
|
||||
LauncherPID: a.mgr.UnifiedPID(),
|
||||
}
|
||||
|
||||
// Launcher stats
|
||||
if msg.LauncherRunning {
|
||||
if stats, err := a.mgr.UnifiedStats(); err == nil {
|
||||
msg.LauncherUptime = formatUptime(stats.UptimeSecs)
|
||||
msg.LauncherMemory = formatBytes(stats.MemRSSKB * 1024)
|
||||
msg.LauncherCPU = fmt.Sprintf("%.1f%%", stats.CPUPct)
|
||||
msg.LauncherLogSize = formatBytes(stats.LogBytes)
|
||||
}
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) startAgent(id string) tea.Cmd {
|
||||
func (a *Adapter) enableAgent(id string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
agents, err := a.mgr.Scan()
|
||||
if err != nil {
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Start", Err: err}
|
||||
}
|
||||
for _, agent := range agents {
|
||||
if agent.ID == id {
|
||||
err = a.mgr.Start(agent)
|
||||
// Give the process a moment to start.
|
||||
if err == nil {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Start", Err: err}
|
||||
}
|
||||
}
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Start", Err: fmt.Errorf("agent not found")}
|
||||
err := a.mgr.ToggleEnabled(id, true)
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Enable", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) stopAgent(id string) tea.Cmd {
|
||||
func (a *Adapter) disableAgent(id string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
err := a.mgr.Stop(id)
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Stop", Err: err}
|
||||
err := a.mgr.ToggleEnabled(id, false)
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Disable", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) killAgent(id string) tea.Cmd {
|
||||
func (a *Adapter) startLauncher() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
err := a.mgr.Kill(id)
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Kill", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) restartAgent(id string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
// Stop first (ignore error if not running)
|
||||
_ = a.mgr.Stop(id)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
agents, err := a.mgr.Scan()
|
||||
if err != nil {
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Restart", Err: err}
|
||||
}
|
||||
for _, agent := range agents {
|
||||
if agent.ID == id {
|
||||
err = a.mgr.Start(agent)
|
||||
if err == nil {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Restart", Err: err}
|
||||
}
|
||||
}
|
||||
return puretui.MsgActionDone{AgentID: id, Action: "Restart", Err: fmt.Errorf("agent not found")}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) startAll() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
agents, err := a.mgr.Scan()
|
||||
if err != nil {
|
||||
return puretui.MsgServerActionDone{Action: "Start All", Errors: []string{err.Error()}, Failed: 1}
|
||||
}
|
||||
var total, failed int
|
||||
var errs []string
|
||||
for _, agent := range agents {
|
||||
if !agent.Enabled {
|
||||
continue
|
||||
}
|
||||
if a.mgr.IsRunning(agent.ID) {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
if err := a.mgr.Start(agent); err != nil {
|
||||
failed++
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", agent.ID, err))
|
||||
}
|
||||
}
|
||||
if total > 0 {
|
||||
err := a.mgr.StartUnified()
|
||||
if err == nil {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return puretui.MsgServerActionDone{Action: "Start All", Total: total, Failed: failed, Errors: errs}
|
||||
return puretui.MsgServerActionDone{Action: "Start", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) stopAll() tea.Cmd {
|
||||
func (a *Adapter) stopLauncher() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
statuses, err := a.mgr.StatusAll()
|
||||
if err != nil {
|
||||
return puretui.MsgServerActionDone{Action: "Stop All", Errors: []string{err.Error()}, Failed: 1}
|
||||
}
|
||||
var total, failed int
|
||||
var errs []string
|
||||
for _, s := range statuses {
|
||||
if !s.Running {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
if err := a.mgr.Stop(s.ID); err != nil {
|
||||
failed++
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", s.ID, err))
|
||||
}
|
||||
}
|
||||
return puretui.MsgServerActionDone{Action: "Stop All", Total: total, Failed: failed, Errors: errs}
|
||||
err := a.mgr.StopUnified()
|
||||
return puretui.MsgServerActionDone{Action: "Stop", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) restartAll() tea.Cmd {
|
||||
func (a *Adapter) restartLauncher() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
agents, err := a.mgr.Scan()
|
||||
if err != nil {
|
||||
return puretui.MsgServerActionDone{Action: "Restart All", Errors: []string{err.Error()}, Failed: 1}
|
||||
}
|
||||
|
||||
// Stop all running first
|
||||
for _, agent := range agents {
|
||||
if agent.Enabled && a.mgr.IsRunning(agent.ID) {
|
||||
_ = a.mgr.Stop(agent.ID)
|
||||
}
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
// Start all enabled
|
||||
var total, failed int
|
||||
var errs []string
|
||||
for _, agent := range agents {
|
||||
if !agent.Enabled {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
if err := a.mgr.Start(agent); err != nil {
|
||||
failed++
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", agent.ID, err))
|
||||
}
|
||||
}
|
||||
if total > 0 {
|
||||
_ = a.mgr.StopUnified()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
err := a.mgr.StartUnified()
|
||||
if err == nil {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return puretui.MsgServerActionDone{Action: "Restart All", Total: total, Failed: failed, Errors: errs}
|
||||
return puretui.MsgServerActionDone{Action: "Restart", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) killAll() tea.Cmd {
|
||||
func (a *Adapter) killLauncher() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
statuses, err := a.mgr.StatusAll()
|
||||
if err != nil {
|
||||
return puretui.MsgServerActionDone{Action: "Kill All", Errors: []string{err.Error()}, Failed: 1}
|
||||
}
|
||||
var total, failed int
|
||||
var errs []string
|
||||
for _, s := range statuses {
|
||||
if !s.Running {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
if err := a.mgr.Kill(s.ID); err != nil {
|
||||
failed++
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", s.ID, err))
|
||||
}
|
||||
}
|
||||
return puretui.MsgServerActionDone{Action: "Kill All", Total: total, Failed: failed, Errors: errs}
|
||||
err := a.mgr.KillUnified()
|
||||
return puretui.MsgServerActionDone{Action: "Kill", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) rebuildRestart() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
// 1. Remember which agents are currently running
|
||||
statuses, err := a.mgr.StatusAll()
|
||||
if err != nil {
|
||||
return puretui.MsgRebuildDone{Errors: []string{err.Error()}}
|
||||
}
|
||||
var wasRunning []string
|
||||
for _, s := range statuses {
|
||||
if s.Running {
|
||||
wasRunning = append(wasRunning, s.ID)
|
||||
}
|
||||
}
|
||||
wasRunning := a.mgr.IsUnifiedRunning()
|
||||
|
||||
// 2. Stop all running agents
|
||||
for _, id := range wasRunning {
|
||||
_ = a.mgr.Stop(id)
|
||||
}
|
||||
if len(wasRunning) > 0 {
|
||||
// Stop if running
|
||||
if wasRunning {
|
||||
_ = a.mgr.StopUnified()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// 3. Build
|
||||
// Build
|
||||
buildOut, buildErr := a.mgr.Build()
|
||||
if buildErr != nil {
|
||||
// Build failed — try to restart what was running before
|
||||
for _, id := range wasRunning {
|
||||
agents, _ := a.mgr.Scan()
|
||||
for _, ag := range agents {
|
||||
if ag.ID == id {
|
||||
_ = a.mgr.Start(ag)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Build failed — try to restart if was running
|
||||
if wasRunning {
|
||||
_ = a.mgr.StartUnified()
|
||||
}
|
||||
// Return last lines of build output
|
||||
lines := strings.Split(strings.TrimSpace(buildOut), "\n")
|
||||
tail := buildOut
|
||||
if len(lines) > 5 {
|
||||
tail = strings.Join(lines[len(lines)-5:], "\n")
|
||||
}
|
||||
return puretui.MsgRebuildDone{BuildLog: tail}
|
||||
return puretui.MsgRebuildDone{BuildOK: false, BuildLog: tail}
|
||||
}
|
||||
|
||||
// 4. Restart only the agents that were running before
|
||||
agents, _ := a.mgr.Scan()
|
||||
agentMap := make(map[string]process.AgentInfo)
|
||||
for _, ag := range agents {
|
||||
agentMap[ag.ID] = ag
|
||||
}
|
||||
|
||||
var restarted, failed int
|
||||
var errs []string
|
||||
for _, id := range wasRunning {
|
||||
ag, ok := agentMap[id]
|
||||
if !ok {
|
||||
failed++
|
||||
errs = append(errs, fmt.Sprintf("%s: not found after build", id))
|
||||
continue
|
||||
// Restart launcher
|
||||
started := false
|
||||
var startErr error
|
||||
if wasRunning {
|
||||
startErr = a.mgr.StartUnified()
|
||||
if startErr == nil {
|
||||
started = true
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
if err := a.mgr.Start(ag); err != nil {
|
||||
failed++
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", id, err))
|
||||
} else {
|
||||
restarted++
|
||||
}
|
||||
}
|
||||
if restarted > 0 {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
return puretui.MsgRebuildDone{
|
||||
BuildOK: true,
|
||||
Restarted: restarted,
|
||||
Failed: failed,
|
||||
Errors: errs,
|
||||
BuildOK: true,
|
||||
Started: started,
|
||||
Err: startErr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) loadLogs(id string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
lines, err := a.mgr.LogTail(id, 100)
|
||||
var lines []string
|
||||
var err error
|
||||
if id == "" {
|
||||
// Launcher logs
|
||||
lines, err = a.mgr.UnifiedLogTail(100)
|
||||
} else {
|
||||
// Agent logs — in unified mode, all go to launcher log
|
||||
lines, err = a.mgr.UnifiedLogTail(100)
|
||||
}
|
||||
if err != nil {
|
||||
return puretui.MsgLogsLoaded{Lines: []string{"Error: " + err.Error()}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user