feat: add --agent overrides to start/attach
This commit is contained in:
@@ -8,17 +8,18 @@ import (
|
||||
|
||||
// Crew command flags
|
||||
var (
|
||||
crewRig string
|
||||
crewBranch bool
|
||||
crewJSON bool
|
||||
crewForce bool
|
||||
crewPurge bool
|
||||
crewNoTmux bool
|
||||
crewDetached bool
|
||||
crewMessage string
|
||||
crewAccount string
|
||||
crewAll bool
|
||||
crewDryRun bool
|
||||
crewRig string
|
||||
crewBranch bool
|
||||
crewJSON bool
|
||||
crewForce bool
|
||||
crewPurge bool
|
||||
crewNoTmux bool
|
||||
crewDetached bool
|
||||
crewMessage string
|
||||
crewAccount string
|
||||
crewAgentOverride string
|
||||
crewAll bool
|
||||
crewDryRun bool
|
||||
)
|
||||
|
||||
var crewCmd = &cobra.Command{
|
||||
@@ -328,6 +329,7 @@ func init() {
|
||||
crewAtCmd.Flags().BoolVar(&crewNoTmux, "no-tmux", false, "Just print directory path")
|
||||
crewAtCmd.Flags().BoolVarP(&crewDetached, "detached", "d", false, "Start session without attaching")
|
||||
crewAtCmd.Flags().StringVar(&crewAccount, "account", "", "Claude Code account handle to use (overrides default)")
|
||||
crewAtCmd.Flags().StringVar(&crewAgentOverride, "agent", "", "Agent alias to run crew worker with (overrides rig/town default)")
|
||||
|
||||
crewRemoveCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
||||
crewRemoveCmd.Flags().BoolVar(&crewForce, "force", false, "Force remove (skip safety checks)")
|
||||
@@ -350,6 +352,7 @@ func init() {
|
||||
|
||||
crewStartCmd.Flags().BoolVar(&crewAll, "all", false, "Start all crew members in the rig")
|
||||
crewStartCmd.Flags().StringVar(&crewAccount, "account", "", "Claude Code account handle to use")
|
||||
crewStartCmd.Flags().StringVar(&crewAgentOverride, "agent", "", "Agent alias to run crew worker with (overrides rig/town default)")
|
||||
|
||||
crewStopCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use (filter when using --all)")
|
||||
crewStopCmd.Flags().BoolVar(&crewAll, "all", false, "Stop all running crew sessions")
|
||||
|
||||
@@ -150,8 +150,11 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
// This gives cleaner lifecycle: Claude exits → session ends (no intermediate shell)
|
||||
// Pass "gt prime" as initial prompt so Claude loads context immediately
|
||||
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
|
||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "gt prime")
|
||||
if err := t.RespawnPane(paneID, claudeCmd); err != nil {
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, "gt prime", crewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.RespawnPane(paneID, startupCmd); err != nil {
|
||||
return fmt.Errorf("starting claude: %w", err)
|
||||
}
|
||||
|
||||
@@ -174,8 +177,11 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
// Use respawn-pane to replace shell with Claude directly
|
||||
// Pass "gt prime" as initial prompt so Claude loads context immediately
|
||||
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
|
||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "gt prime")
|
||||
if err := t.RespawnPane(paneID, claudeCmd); err != nil {
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, "gt prime", crewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.RespawnPane(paneID, startupCmd); err != nil {
|
||||
return fmt.Errorf("restarting claude: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -185,7 +191,10 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
if isInTmuxSession(sessionID) {
|
||||
// We're in the session at a shell prompt - just start the agent directly
|
||||
// Pass "gt prime" as initial prompt so it loads context immediately
|
||||
agentCfg := config.ResolveAgentConfig(townRoot, r.Path)
|
||||
agentCfg, _, err := config.ResolveAgentConfigWithOverride(townRoot, r.Path, crewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving agent: %w", err)
|
||||
}
|
||||
fmt.Printf("Starting %s in current session...\n", agentCfg.Command)
|
||||
return execAgent(agentCfg, "gt prime")
|
||||
}
|
||||
|
||||
@@ -338,6 +338,7 @@ func runCrewStart(cmd *cobra.Command, args []string) error {
|
||||
// Set the start.go flags before calling runStartCrew
|
||||
startCrewRig = rigName
|
||||
startCrewAccount = crewAccount
|
||||
startCrewAgentOverride = crewAgentOverride
|
||||
|
||||
// Use rig/name format for runStartCrew
|
||||
fullName := rigName + "/" + name
|
||||
|
||||
@@ -89,6 +89,8 @@ Stops the current session (if running) and starts a fresh one.`,
|
||||
RunE: runDeaconRestart,
|
||||
}
|
||||
|
||||
var deaconAgentOverride string
|
||||
|
||||
var deaconHeartbeatCmd = &cobra.Command{
|
||||
Use: "heartbeat [action]",
|
||||
Short: "Update the Deacon heartbeat",
|
||||
@@ -203,7 +205,6 @@ Examples:
|
||||
RunE: runDeaconStaleHooks,
|
||||
}
|
||||
|
||||
|
||||
var (
|
||||
triggerTimeout time.Duration
|
||||
|
||||
@@ -258,6 +259,10 @@ func init() {
|
||||
deaconStaleHooksCmd.Flags().BoolVar(&staleHooksDryRun, "dry-run", false,
|
||||
"Preview what would be unhooked without making changes")
|
||||
|
||||
deaconStartCmd.Flags().StringVar(&deaconAgentOverride, "agent", "", "Agent alias to run the Deacon with (overrides town default)")
|
||||
deaconAttachCmd.Flags().StringVar(&deaconAgentOverride, "agent", "", "Agent alias to run the Deacon with (overrides town default)")
|
||||
deaconRestartCmd.Flags().StringVar(&deaconAgentOverride, "agent", "", "Agent alias to run the Deacon with (overrides town default)")
|
||||
|
||||
rootCmd.AddCommand(deaconCmd)
|
||||
}
|
||||
|
||||
@@ -275,7 +280,7 @@ func runDeaconStart(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("Deacon session already running. Attach with: gt deacon attach")
|
||||
}
|
||||
|
||||
if err := startDeaconSession(t, sessionName); err != nil {
|
||||
if err := startDeaconSession(t, sessionName, deaconAgentOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -287,7 +292,7 @@ func runDeaconStart(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// startDeaconSession creates and initializes the Deacon tmux session.
|
||||
func startDeaconSession(t *tmux.Tmux, sessionName string) error {
|
||||
func startDeaconSession(t *tmux.Tmux, sessionName, agentOverride string) error {
|
||||
// Find workspace root
|
||||
townRoot, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
@@ -326,7 +331,11 @@ func startDeaconSession(t *tmux.Tmux, sessionName string) error {
|
||||
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat
|
||||
// The startup hook handles context loading automatically
|
||||
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
|
||||
if err := t.SendKeys(sessionName, config.BuildAgentStartupCommand("deacon", "deacon", "", "")); err != nil {
|
||||
startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("deacon", "deacon", "", "", agentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.SendKeys(sessionName, startupCmd); err != nil {
|
||||
return fmt.Errorf("sending command: %w", err)
|
||||
}
|
||||
|
||||
@@ -394,7 +403,7 @@ func runDeaconAttach(cmd *cobra.Command, args []string) error {
|
||||
if !running {
|
||||
// Auto-start if not running
|
||||
fmt.Println("Deacon session not running, starting...")
|
||||
if err := startDeaconSession(t, sessionName); err != nil {
|
||||
if err := startDeaconSession(t, sessionName, deaconAgentOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -942,4 +951,3 @@ func runDeaconStaleHooks(cmd *cobra.Command, args []string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ The Mayor is the global coordinator for Gas Town, running as a persistent
|
||||
tmux session. Use the subcommands to start, stop, attach, and check status.`,
|
||||
}
|
||||
|
||||
var mayorAgentOverride string
|
||||
|
||||
var mayorStartCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start the Mayor session",
|
||||
@@ -84,6 +86,10 @@ func init() {
|
||||
mayorCmd.AddCommand(mayorStatusCmd)
|
||||
mayorCmd.AddCommand(mayorRestartCmd)
|
||||
|
||||
mayorStartCmd.Flags().StringVar(&mayorAgentOverride, "agent", "", "Agent alias to run the Mayor with (overrides town default)")
|
||||
mayorAttachCmd.Flags().StringVar(&mayorAgentOverride, "agent", "", "Agent alias to run the Mayor with (overrides town default)")
|
||||
mayorRestartCmd.Flags().StringVar(&mayorAgentOverride, "agent", "", "Agent alias to run the Mayor with (overrides town default)")
|
||||
|
||||
rootCmd.AddCommand(mayorCmd)
|
||||
}
|
||||
|
||||
@@ -101,7 +107,7 @@ func runMayorStart(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("Mayor session already running. Attach with: gt mayor attach")
|
||||
}
|
||||
|
||||
if err := startMayorSession(t, sessionName); err != nil {
|
||||
if err := startMayorSession(t, sessionName, mayorAgentOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -113,7 +119,7 @@ func runMayorStart(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// startMayorSession creates and initializes the Mayor tmux session.
|
||||
func startMayorSession(t *tmux.Tmux, sessionName string) error {
|
||||
func startMayorSession(t *tmux.Tmux, sessionName, agentOverride string) error {
|
||||
// Find workspace root
|
||||
townRoot, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
@@ -139,8 +145,11 @@ func startMayorSession(t *tmux.Tmux, sessionName string) error {
|
||||
// Use SendKeysDelayed to allow shell initialization after NewSession
|
||||
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
|
||||
// Mayor uses default runtime config (empty rigPath) since it's not rig-specific
|
||||
claudeCmd := config.BuildAgentStartupCommand("mayor", "mayor", "", "")
|
||||
if err := t.SendKeysDelayed(sessionName, claudeCmd, 200); err != nil {
|
||||
startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", "", agentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.SendKeysDelayed(sessionName, startupCmd, 200); err != nil {
|
||||
return fmt.Errorf("sending command: %w", err)
|
||||
}
|
||||
|
||||
@@ -208,7 +217,7 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
||||
if !running {
|
||||
// Auto-start if not running
|
||||
fmt.Println("Mayor session not running, starting...")
|
||||
if err := startMayorSession(t, sessionName); err != nil {
|
||||
if err := startMayorSession(t, sessionName, mayorAgentOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,18 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
startAll bool
|
||||
startCrewRig string
|
||||
startCrewAccount string
|
||||
shutdownGraceful bool
|
||||
shutdownWait int
|
||||
shutdownAll bool
|
||||
shutdownForce bool
|
||||
shutdownYes bool
|
||||
shutdownPolecatsOnly bool
|
||||
shutdownNuclear bool
|
||||
startAll bool
|
||||
startAgentOverride string
|
||||
startCrewRig string
|
||||
startCrewAccount string
|
||||
startCrewAgentOverride string
|
||||
shutdownGraceful bool
|
||||
shutdownWait int
|
||||
shutdownAll bool
|
||||
shutdownForce bool
|
||||
shutdownYes bool
|
||||
shutdownPolecatsOnly bool
|
||||
shutdownNuclear bool
|
||||
)
|
||||
|
||||
var startCmd = &cobra.Command{
|
||||
@@ -104,9 +106,11 @@ Examples:
|
||||
func init() {
|
||||
startCmd.Flags().BoolVarP(&startAll, "all", "a", false,
|
||||
"Also start Witnesses and Refineries for all rigs")
|
||||
startCmd.Flags().StringVar(&startAgentOverride, "agent", "", "Agent alias to run Mayor/Deacon with (overrides town default)")
|
||||
|
||||
startCrewCmd.Flags().StringVar(&startCrewRig, "rig", "", "Rig to use")
|
||||
startCrewCmd.Flags().StringVar(&startCrewAccount, "account", "", "Claude Code account handle to use")
|
||||
startCrewCmd.Flags().StringVar(&startCrewAgentOverride, "agent", "", "Agent alias to run crew worker with (overrides rig/town default)")
|
||||
startCmd.AddCommand(startCrewCmd)
|
||||
|
||||
shutdownCmd.Flags().BoolVarP(&shutdownGraceful, "graceful", "g", false,
|
||||
@@ -155,7 +159,7 @@ func runStart(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Starting Gas Town from %s\n\n", style.Dim.Render(townRoot))
|
||||
|
||||
// Start core agents (Mayor and Deacon)
|
||||
if err := startCoreAgents(t); err != nil {
|
||||
if err := startCoreAgents(t, startAgentOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -182,7 +186,7 @@ func runStart(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// startCoreAgents starts Mayor and Deacon sessions.
|
||||
func startCoreAgents(t *tmux.Tmux) error {
|
||||
func startCoreAgents(t *tmux.Tmux, agentOverride string) error {
|
||||
// Get session names
|
||||
mayorSession := getMayorSessionName()
|
||||
deaconSession := getDeaconSessionName()
|
||||
@@ -193,7 +197,7 @@ func startCoreAgents(t *tmux.Tmux) error {
|
||||
fmt.Printf(" %s Mayor already running\n", style.Dim.Render("○"))
|
||||
} else {
|
||||
fmt.Printf(" %s Starting Mayor...\n", style.Bold.Render("→"))
|
||||
if err := startMayorSession(t, mayorSession); err != nil {
|
||||
if err := startMayorSession(t, mayorSession, agentOverride); err != nil {
|
||||
return fmt.Errorf("starting Mayor: %w", err)
|
||||
}
|
||||
fmt.Printf(" %s Mayor started\n", style.Bold.Render("✓"))
|
||||
@@ -205,7 +209,7 @@ func startCoreAgents(t *tmux.Tmux) error {
|
||||
fmt.Printf(" %s Deacon already running\n", style.Dim.Render("○"))
|
||||
} else {
|
||||
fmt.Printf(" %s Starting Deacon...\n", style.Bold.Render("→"))
|
||||
if err := startDeaconSession(t, deaconSession); err != nil {
|
||||
if err := startDeaconSession(t, deaconSession, agentOverride); err != nil {
|
||||
return fmt.Errorf("starting Deacon: %w", err)
|
||||
}
|
||||
fmt.Printf(" %s Deacon started\n", style.Bold.Render("✓"))
|
||||
@@ -799,8 +803,11 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
if !t.IsClaudeRunning(sessionID) {
|
||||
// Claude has exited, restart it with "gt prime" as initial prompt
|
||||
fmt.Printf("Session exists, restarting Claude...\n")
|
||||
claudeCmd := config.BuildCrewStartupCommand(rigName, name, r.Path, "gt prime")
|
||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.SendKeys(sessionID, startupCmd); err != nil {
|
||||
return fmt.Errorf("restarting claude: %w", err)
|
||||
}
|
||||
} else {
|
||||
@@ -833,8 +840,11 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Start claude with skip permissions and proper env vars for seance
|
||||
// Pass "gt prime" as initial prompt so context is loaded immediately
|
||||
claudeCmd := config.BuildCrewStartupCommand(rigName, name, r.Path, "gt prime")
|
||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.SendKeys(sessionID, startupCmd); err != nil {
|
||||
return fmt.Errorf("starting claude: %w", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user