feat: add rebuild and restart functionality for agents, including build process and status reporting
This commit is contained in:
@@ -259,6 +259,12 @@ func (a *Agent) runLLM(ctx context.Context, msgCtx decision.MessageContext) (str
|
|||||||
"call_id", tc.ID,
|
"call_id", tc.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Notify the room that a tool is being called
|
||||||
|
toolNotice := fmt.Sprintf("🔨 <em>%s</em>", tc.Name)
|
||||||
|
if err := a.matrix.SendMarkdown(ctx, msgCtx.RoomID, toolNotice); err != nil {
|
||||||
|
a.logger.Warn("failed to send tool call notice", "tool", tc.Name, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
result := a.toolReg.Execute(ctx, tc.Name, tc.Arguments)
|
result := a.toolReg.Execute(ctx, tc.Name, tc.Arguments)
|
||||||
|
|
||||||
output := result.Output
|
output := result.Output
|
||||||
|
|||||||
@@ -24,5 +24,14 @@ type MsgServerActionDone struct {
|
|||||||
Errors []string
|
Errors []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MsgRebuildDone reports the result of a rebuild & restart cycle.
|
||||||
|
type MsgRebuildDone struct {
|
||||||
|
BuildOK bool
|
||||||
|
BuildLog string // last lines of build output
|
||||||
|
Restarted int // agents restarted after build
|
||||||
|
Failed int
|
||||||
|
Errors []string
|
||||||
|
}
|
||||||
|
|
||||||
// MsgTick triggers a periodic refresh.
|
// MsgTick triggers a periodic refresh.
|
||||||
type MsgTick struct{}
|
type MsgTick struct{}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func ServerMenuOptions() []MenuOption {
|
|||||||
{Label: "Stop All", Desc: "Detener todos los agentes"},
|
{Label: "Stop All", Desc: "Detener todos los agentes"},
|
||||||
{Label: "Restart All", Desc: "Reiniciar todos los agentes"},
|
{Label: "Restart All", Desc: "Reiniciar todos los agentes"},
|
||||||
{Label: "Kill All", Desc: "SIGKILL forzado a todos"},
|
{Label: "Kill All", Desc: "SIGKILL forzado a todos"},
|
||||||
|
{Label: "Rebuild & Restart", Desc: "Build + reiniciar activos"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-4
@@ -16,10 +16,11 @@ const (
|
|||||||
IntentQuit IntentKind = "quit"
|
IntentQuit IntentKind = "quit"
|
||||||
|
|
||||||
// Server-wide bulk operations
|
// Server-wide bulk operations
|
||||||
IntentStartAll IntentKind = "start_all"
|
IntentStartAll IntentKind = "start_all"
|
||||||
IntentStopAll IntentKind = "stop_all"
|
IntentStopAll IntentKind = "stop_all"
|
||||||
IntentRestartAll IntentKind = "restart_all"
|
IntentRestartAll IntentKind = "restart_all"
|
||||||
IntentKillAll IntentKind = "kill_all"
|
IntentKillAll IntentKind = "kill_all"
|
||||||
|
IntentRebuildRestart IntentKind = "rebuild_restart"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Intent is pure data describing a side effect to execute.
|
// Intent is pure data describing a side effect to execute.
|
||||||
@@ -67,6 +68,16 @@ func Update(model Model, msg interface{}) (Model, []Intent) {
|
|||||||
}
|
}
|
||||||
return model, []Intent{{Kind: IntentLoadAgents}}
|
return model, []Intent{{Kind: IntentLoadAgents}}
|
||||||
|
|
||||||
|
case MsgRebuildDone:
|
||||||
|
if !m.BuildOK {
|
||||||
|
model.StatusMsg = fmt.Sprintf("Build failed: %s", m.BuildLog)
|
||||||
|
} else if m.Failed > 0 {
|
||||||
|
model.StatusMsg = fmt.Sprintf("Built OK, %d/%d agents failed to restart", m.Failed, m.Restarted+m.Failed)
|
||||||
|
} else {
|
||||||
|
model.StatusMsg = fmt.Sprintf("Built OK, %d agents restarted", m.Restarted)
|
||||||
|
}
|
||||||
|
return model, []Intent{{Kind: IntentLoadAgents}}
|
||||||
|
|
||||||
case MsgServerActionDone:
|
case MsgServerActionDone:
|
||||||
if m.Failed == 0 {
|
if m.Failed == 0 {
|
||||||
model.StatusMsg = fmt.Sprintf("%s: %d agents OK", m.Action, m.Total)
|
model.StatusMsg = fmt.Sprintf("%s: %d agents OK", m.Action, m.Total)
|
||||||
@@ -267,6 +278,9 @@ func executeServerAction(model Model, action string) (Model, []Intent) {
|
|||||||
case "Kill All":
|
case "Kill All":
|
||||||
model.StatusMsg = "Killing all agents..."
|
model.StatusMsg = "Killing all agents..."
|
||||||
return model, []Intent{{Kind: IntentKillAll}}
|
return model, []Intent{{Kind: IntentKillAll}}
|
||||||
|
case "Rebuild & Restart":
|
||||||
|
model.StatusMsg = "Building & restarting..."
|
||||||
|
return model, []Intent{{Kind: IntentRebuildRestart}}
|
||||||
}
|
}
|
||||||
return model, nil
|
return model, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,6 +379,15 @@ func (m *Manager) PidPath(id string) string { return m.pidPath(id) }
|
|||||||
// LogPath returns the path to the log file for an agent.
|
// LogPath returns the path to the log file for an agent.
|
||||||
func (m *Manager) LogPath(id string) string { return m.logPath(id) }
|
func (m *Manager) LogPath(id string) string { return m.logPath(id) }
|
||||||
|
|
||||||
|
// Build compiles all project binaries by running build.sh.
|
||||||
|
// Returns the combined output and any error.
|
||||||
|
func (m *Manager) Build() (string, error) {
|
||||||
|
cmd := exec.Command("bash", "build.sh")
|
||||||
|
cmd.Env = m.buildEnv()
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
|
||||||
// ── internal helpers ─────────────────────────────────────────────────────
|
// ── internal helpers ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (m *Manager) pidPath(id string) string { return filepath.Join(m.runDir, id+".pid") }
|
func (m *Manager) pidPath(id string) string { return filepath.Join(m.runDir, id+".pid") }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -56,6 +57,9 @@ func (a *Adapter) RunIntent(intent puretui.Intent) tea.Cmd {
|
|||||||
case puretui.IntentKillAll:
|
case puretui.IntentKillAll:
|
||||||
return a.killAll()
|
return a.killAll()
|
||||||
|
|
||||||
|
case puretui.IntentRebuildRestart:
|
||||||
|
return a.rebuildRestart()
|
||||||
|
|
||||||
case puretui.IntentTick:
|
case puretui.IntentTick:
|
||||||
return a.tick()
|
return a.tick()
|
||||||
|
|
||||||
@@ -267,6 +271,86 @@ func (a *Adapter) killAll() tea.Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Stop all running agents
|
||||||
|
for _, id := range wasRunning {
|
||||||
|
_ = a.mgr.Stop(id)
|
||||||
|
}
|
||||||
|
if len(wasRunning) > 0 {
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Adapter) loadLogs(id string) tea.Cmd {
|
func (a *Adapter) loadLogs(id string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
lines, err := a.mgr.LogTail(id, 100)
|
lines, err := a.mgr.LogTail(id, 100)
|
||||||
|
|||||||
Reference in New Issue
Block a user