fix: restore agent override support lost in manager refactors
The manager refactors (ea8bef2,72544cc0) conflicted with the agent override feature, causing regressions: Deacon (ea8bef2): - Lost agentOverride parameter - Re-added respawn loop (removed in5f2e16f) - Lost GUPP (startup + propulsion nudges) Crew (72544cc0): - Lost agentOverride wiring to StartOptions - --agent flag had no effect on crew refresh/restart This fix restores agent override support and GUPP while keeping improvements from the manager refactors (zombie detection, etc). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
00a59dec44
commit
c91ab85457
@@ -236,9 +236,10 @@ func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// Use manager's Start() with refresh options
|
// Use manager's Start() with refresh options
|
||||||
err = crewMgr.Start(name, crew.StartOptions{
|
err = crewMgr.Start(name, crew.StartOptions{
|
||||||
KillExisting: true, // Kill old session if running
|
KillExisting: true, // Kill old session if running
|
||||||
Topic: "refresh", // Startup nudge topic
|
Topic: "refresh", // Startup nudge topic
|
||||||
Interactive: true, // No --dangerously-skip-permissions
|
Interactive: true, // No --dangerously-skip-permissions
|
||||||
|
AgentOverride: crewAgentOverride,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("starting crew session: %w", err)
|
return fmt.Errorf("starting crew session: %w", err)
|
||||||
@@ -346,8 +347,9 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
// Use manager's Start() with restart options
|
// Use manager's Start() with restart options
|
||||||
// Start() will create workspace if needed (idempotent)
|
// Start() will create workspace if needed (idempotent)
|
||||||
err = crewMgr.Start(name, crew.StartOptions{
|
err = crewMgr.Start(name, crew.StartOptions{
|
||||||
KillExisting: true, // Kill old session if running
|
KillExisting: true, // Kill old session if running
|
||||||
Topic: "restart", // Startup nudge topic
|
Topic: "restart", // Startup nudge topic
|
||||||
|
AgentOverride: crewAgentOverride,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error restarting %s: %v\n", arg, err)
|
fmt.Printf("Error restarting %s: %v\n", arg, err)
|
||||||
@@ -425,8 +427,9 @@ func runCrewRestartAll() error {
|
|||||||
|
|
||||||
// Use manager's Start() with restart options
|
// Use manager's Start() with restart options
|
||||||
err = crewMgr.Start(agent.AgentName, crew.StartOptions{
|
err = crewMgr.Start(agent.AgentName, crew.StartOptions{
|
||||||
KillExisting: true, // Kill old session if running
|
KillExisting: true, // Kill old session if running
|
||||||
Topic: "restart", // Startup nudge topic
|
Topic: "restart", // Startup nudge topic
|
||||||
|
AgentOverride: crewAgentOverride,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failed++
|
failed++
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ func startCoreAgents(townRoot string, agentOverride string) error {
|
|||||||
|
|
||||||
// Start Deacon (health monitor)
|
// Start Deacon (health monitor)
|
||||||
deaconMgr := deacon.NewManager(townRoot)
|
deaconMgr := deacon.NewManager(townRoot)
|
||||||
if err := deaconMgr.Start(); err != nil {
|
if err := deaconMgr.Start(agentOverride); err != nil {
|
||||||
if err == deacon.ErrAlreadyRunning {
|
if err == deacon.ErrAlreadyRunning {
|
||||||
fmt.Printf(" %s Deacon already running\n", style.Dim.Render("○"))
|
fmt.Printf(" %s Deacon already running\n", style.Dim.Render("○"))
|
||||||
} else {
|
} else {
|
||||||
@@ -694,6 +694,7 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
|||||||
err = crewMgr.Start(name, crew.StartOptions{
|
err = crewMgr.Start(name, crew.StartOptions{
|
||||||
Account: startCrewAccount,
|
Account: startCrewAccount,
|
||||||
ClaudeConfigDir: claudeConfigDir,
|
ClaudeConfigDir: claudeConfigDir,
|
||||||
|
AgentOverride: startCrewAgentOverride,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, crew.ErrSessionRunning) {
|
if errors.Is(err, crew.ErrSessionRunning) {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func runUp(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// 2. Deacon (Claude agent)
|
// 2. Deacon (Claude agent)
|
||||||
deaconMgr := deacon.NewManager(townRoot)
|
deaconMgr := deacon.NewManager(townRoot)
|
||||||
if err := deaconMgr.Start(); err != nil {
|
if err := deaconMgr.Start(""); err != nil {
|
||||||
if err == deacon.ErrAlreadyRunning {
|
if err == deacon.ErrAlreadyRunning {
|
||||||
printStatus("Deacon", true, deaconMgr.SessionName())
|
printStatus("Deacon", true, deaconMgr.SessionName())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ type StartOptions struct {
|
|||||||
|
|
||||||
// Interactive removes --dangerously-skip-permissions for interactive/refresh mode.
|
// Interactive removes --dangerously-skip-permissions for interactive/refresh mode.
|
||||||
Interactive bool
|
Interactive bool
|
||||||
|
|
||||||
|
// AgentOverride specifies an alternate agent alias (e.g., for testing).
|
||||||
|
AgentOverride string
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCrewName checks that a crew name is safe and valid.
|
// validateCrewName checks that a crew name is safe and valid.
|
||||||
@@ -514,7 +517,11 @@ func (m *Manager) Start(name string, opts StartOptions) error {
|
|||||||
|
|
||||||
// Start claude with environment exports and beacon as initial prompt
|
// Start claude with environment exports and beacon as initial prompt
|
||||||
// SessionStart hook handles context loading (gt prime --hook)
|
// SessionStart hook handles context loading (gt prime --hook)
|
||||||
claudeCmd := config.BuildCrewStartupCommand(m.rig.Name, name, m.rig.Path, beacon)
|
claudeCmd, err := config.BuildCrewStartupCommandWithAgentOverride(m.rig.Name, name, m.rig.Path, beacon, opts.AgentOverride)
|
||||||
|
if err != nil {
|
||||||
|
_ = t.KillSession(sessionID)
|
||||||
|
return fmt.Errorf("building startup command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// For interactive/refresh mode, remove --dangerously-skip-permissions
|
// For interactive/refresh mode, remove --dangerously-skip-permissions
|
||||||
if opts.Interactive {
|
if opts.Interactive {
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ func (m *Manager) deaconDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the deacon session.
|
// Start starts the deacon session.
|
||||||
// The deacon runs in a respawn loop for automatic recovery.
|
// agentOverride allows specifying an alternate agent alias (e.g., for testing).
|
||||||
func (m *Manager) Start() error {
|
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat.
|
||||||
|
func (m *Manager) Start(agentOverride string) error {
|
||||||
t := tmux.NewTmux()
|
t := tmux.NewTmux()
|
||||||
sessionID := m.SessionName()
|
sessionID := m.SessionName()
|
||||||
|
|
||||||
@@ -91,26 +92,25 @@ func (m *Manager) Start() error {
|
|||||||
theme := tmux.DeaconTheme()
|
theme := tmux.DeaconTheme()
|
||||||
_ = t.ConfigureGasTownSession(sessionID, theme, "", "Deacon", "health-check")
|
_ = t.ConfigureGasTownSession(sessionID, theme, "", "Deacon", "health-check")
|
||||||
|
|
||||||
// Launch Claude in a respawn loop for automatic recovery
|
// Launch Claude directly (no shell respawn loop)
|
||||||
// The respawn loop ensures the deacon restarts if Claude crashes
|
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat
|
||||||
runtimeCmd := config.GetRuntimeCommand("")
|
startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("deacon", "deacon", "", "", agentOverride)
|
||||||
respawnCmd := fmt.Sprintf(
|
if err != nil {
|
||||||
`export GT_ROLE=deacon BD_ACTOR=deacon GIT_AUTHOR_NAME=deacon && while true; do echo "⛪ Starting Deacon session..."; %s; echo ""; echo "Deacon exited. Restarting in 2s... (Ctrl-C to stop)"; sleep 2; done`,
|
_ = t.KillSession(sessionID)
|
||||||
runtimeCmd,
|
return fmt.Errorf("building startup command: %w", err)
|
||||||
)
|
}
|
||||||
|
|
||||||
// Wait for shell to be ready before sending keys (prevents "can't find pane" under load)
|
// Wait for shell to be ready before sending keys (prevents "can't find pane" under load)
|
||||||
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
||||||
_ = t.KillSession(sessionID)
|
_ = t.KillSession(sessionID)
|
||||||
return fmt.Errorf("waiting for shell: %w", err)
|
return fmt.Errorf("waiting for shell: %w", err)
|
||||||
}
|
}
|
||||||
if err := t.SendKeysDelayed(sessionID, respawnCmd, 200); err != nil {
|
if err := t.SendKeysDelayed(sessionID, startupCmd, 200); err != nil {
|
||||||
_ = t.KillSession(sessionID) // best-effort cleanup
|
_ = t.KillSession(sessionID) // best-effort cleanup
|
||||||
return fmt.Errorf("starting Claude agent: %w", err)
|
return fmt.Errorf("starting Claude agent: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Claude to start (non-fatal)
|
// Wait for Claude to start (non-fatal)
|
||||||
// Note: Deacon respawn loop makes this tricky - Claude restarts multiple times
|
|
||||||
if err := t.WaitForCommand(sessionID, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
if err := t.WaitForCommand(sessionID, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||||
// Non-fatal - try to continue anyway
|
// Non-fatal - try to continue anyway
|
||||||
}
|
}
|
||||||
@@ -120,8 +120,18 @@ func (m *Manager) Start() error {
|
|||||||
|
|
||||||
time.Sleep(constants.ShutdownNotifyDelay)
|
time.Sleep(constants.ShutdownNotifyDelay)
|
||||||
|
|
||||||
// Note: Deacon doesn't get startup nudge due to respawn loop complexity
|
// Inject startup nudge for predecessor discovery via /resume
|
||||||
// The deacon uses its own patrol pattern defined in its CLAUDE.md/prime
|
_ = session.StartupNudge(t, sessionID, session.StartupNudgeConfig{
|
||||||
|
Recipient: "deacon",
|
||||||
|
Sender: "daemon",
|
||||||
|
Topic: "patrol",
|
||||||
|
}) // Non-fatal
|
||||||
|
|
||||||
|
// GUPP: Gas Town Universal Propulsion Principle
|
||||||
|
// Send the propulsion nudge to trigger autonomous patrol execution.
|
||||||
|
// Wait for beacon to be fully processed (needs to be separate prompt)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
_ = t.NudgeSession(sessionID, session.PropulsionNudgeForRole("deacon", deaconDir)) // Non-fatal
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user