feat(daemon): respect rig operational state for auto-start
Update daemon to check rig config before auto-starting agents: - Check wisp config "status" - skip if parked or docked - Check "auto_restart" config - skip if blocked or false - Log skip reason for visibility Affects ensureWitnessRunning, ensureRefineryRunning, restartPolecatSession, and lifecycle restartSession. (gt-68c46) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/rig"
|
"github.com/steveyegge/gastown/internal/rig"
|
||||||
"github.com/steveyegge/gastown/internal/session"
|
"github.com/steveyegge/gastown/internal/session"
|
||||||
"github.com/steveyegge/gastown/internal/tmux"
|
"github.com/steveyegge/gastown/internal/tmux"
|
||||||
|
"github.com/steveyegge/gastown/internal/wisp"
|
||||||
"github.com/steveyegge/gastown/internal/witness"
|
"github.com/steveyegge/gastown/internal/witness"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -399,6 +400,12 @@ func (d *Daemon) ensureWitnessesRunning() {
|
|||||||
// ensureWitnessRunning ensures the witness for a specific rig is running.
|
// ensureWitnessRunning ensures the witness for a specific rig is running.
|
||||||
// Discover, don't track: uses Manager.Start() which checks tmux directly (gt-zecmc).
|
// Discover, don't track: uses Manager.Start() which checks tmux directly (gt-zecmc).
|
||||||
func (d *Daemon) ensureWitnessRunning(rigName string) {
|
func (d *Daemon) ensureWitnessRunning(rigName string) {
|
||||||
|
// Check rig operational state before auto-starting
|
||||||
|
if operational, reason := d.isRigOperational(rigName); !operational {
|
||||||
|
d.logger.Printf("Skipping witness auto-start for %s: %s", rigName, reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Manager.Start() handles: zombie detection, session creation, env vars, theming,
|
// Manager.Start() handles: zombie detection, session creation, env vars, theming,
|
||||||
// WaitForClaudeReady, and crucially - startup/propulsion nudges (GUPP).
|
// WaitForClaudeReady, and crucially - startup/propulsion nudges (GUPP).
|
||||||
// It returns ErrAlreadyRunning if Claude is already running in tmux.
|
// It returns ErrAlreadyRunning if Claude is already running in tmux.
|
||||||
@@ -432,6 +439,12 @@ func (d *Daemon) ensureRefineriesRunning() {
|
|||||||
// ensureRefineryRunning ensures the refinery for a specific rig is running.
|
// ensureRefineryRunning ensures the refinery for a specific rig is running.
|
||||||
// Discover, don't track: uses Manager.Start() which checks tmux directly (gt-zecmc).
|
// Discover, don't track: uses Manager.Start() which checks tmux directly (gt-zecmc).
|
||||||
func (d *Daemon) ensureRefineryRunning(rigName string) {
|
func (d *Daemon) ensureRefineryRunning(rigName string) {
|
||||||
|
// Check rig operational state before auto-starting
|
||||||
|
if operational, reason := d.isRigOperational(rigName); !operational {
|
||||||
|
d.logger.Printf("Skipping refinery auto-start for %s: %s", rigName, reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Manager.Start() handles: zombie detection, session creation, env vars, theming,
|
// Manager.Start() handles: zombie detection, session creation, env vars, theming,
|
||||||
// WaitForClaudeReady, and crucially - startup/propulsion nudges (GUPP).
|
// WaitForClaudeReady, and crucially - startup/propulsion nudges (GUPP).
|
||||||
// It returns ErrAlreadyRunning if Claude is already running in tmux.
|
// It returns ErrAlreadyRunning if Claude is already running in tmux.
|
||||||
@@ -475,6 +488,39 @@ func (d *Daemon) getKnownRigs() []string {
|
|||||||
return rigs
|
return rigs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isRigOperational checks if a rig is in an operational state.
|
||||||
|
// Returns true if the rig can have agents auto-started.
|
||||||
|
// Returns false (with reason) if the rig is parked, docked, or has auto_restart blocked/disabled.
|
||||||
|
func (d *Daemon) isRigOperational(rigName string) (bool, string) {
|
||||||
|
cfg := wisp.NewConfig(d.config.TownRoot, rigName)
|
||||||
|
|
||||||
|
// Check rig status - parked and docked rigs should not have agents auto-started
|
||||||
|
status := cfg.GetString("status")
|
||||||
|
switch status {
|
||||||
|
case "parked":
|
||||||
|
return false, "rig is parked"
|
||||||
|
case "docked":
|
||||||
|
return false, "rig is docked"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check auto_restart config
|
||||||
|
// If explicitly blocked (nil), auto-restart is disabled
|
||||||
|
if cfg.IsBlocked("auto_restart") {
|
||||||
|
return false, "auto_restart is blocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If explicitly set to false, auto-restart is disabled
|
||||||
|
// Note: GetBool returns false for unset keys, so we need to check if it's explicitly set
|
||||||
|
val := cfg.Get("auto_restart")
|
||||||
|
if val != nil {
|
||||||
|
if autoRestart, ok := val.(bool); ok && !autoRestart {
|
||||||
|
return false, "auto_restart is disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
// triggerPendingSpawns polls pending polecat spawns and triggers those that are ready.
|
// triggerPendingSpawns polls pending polecat spawns and triggers those that are ready.
|
||||||
// This is bootstrap mode - uses regex-based WaitForClaudeReady which is acceptable
|
// This is bootstrap mode - uses regex-based WaitForClaudeReady which is acceptable
|
||||||
// for daemon operations when no AI agent is guaranteed to be running.
|
// for daemon operations when no AI agent is guaranteed to be running.
|
||||||
@@ -708,6 +754,11 @@ func (d *Daemon) checkPolecatHealth(rigName, polecatName string) {
|
|||||||
|
|
||||||
// restartPolecatSession restarts a crashed polecat session.
|
// restartPolecatSession restarts a crashed polecat session.
|
||||||
func (d *Daemon) restartPolecatSession(rigName, polecatName, sessionName string) error {
|
func (d *Daemon) restartPolecatSession(rigName, polecatName, sessionName string) error {
|
||||||
|
// Check rig operational state before auto-restarting
|
||||||
|
if operational, reason := d.isRigOperational(rigName); !operational {
|
||||||
|
return fmt.Errorf("cannot restart polecat: %s", reason)
|
||||||
|
}
|
||||||
|
|
||||||
// Determine working directory
|
// Determine working directory
|
||||||
workDir := filepath.Join(d.config.TownRoot, rigName, "polecats", polecatName)
|
workDir := filepath.Join(d.config.TownRoot, rigName, "polecats", polecatName)
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,15 @@ func (d *Daemon) restartSession(sessionName, identity string) error {
|
|||||||
return fmt.Errorf("parsing identity: %w", err)
|
return fmt.Errorf("parsing identity: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check rig operational state for rig-level agents (witness, refinery, crew, polecat)
|
||||||
|
// Town-level agents (mayor, deacon) are not affected by rig state
|
||||||
|
if parsed.RigName != "" {
|
||||||
|
if operational, reason := d.isRigOperational(parsed.RigName); !operational {
|
||||||
|
d.logger.Printf("Skipping session restart for %s: %s", identity, reason)
|
||||||
|
return fmt.Errorf("cannot restart session: %s", reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine working directory
|
// Determine working directory
|
||||||
workDir := d.getWorkDir(config, parsed)
|
workDir := d.getWorkDir(config, parsed)
|
||||||
if workDir == "" {
|
if workDir == "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user