feat: add --agent overrides to start/attach
This commit is contained in:
+14
-11
@@ -8,17 +8,18 @@ import (
|
|||||||
|
|
||||||
// Crew command flags
|
// Crew command flags
|
||||||
var (
|
var (
|
||||||
crewRig string
|
crewRig string
|
||||||
crewBranch bool
|
crewBranch bool
|
||||||
crewJSON bool
|
crewJSON bool
|
||||||
crewForce bool
|
crewForce bool
|
||||||
crewPurge bool
|
crewPurge bool
|
||||||
crewNoTmux bool
|
crewNoTmux bool
|
||||||
crewDetached bool
|
crewDetached bool
|
||||||
crewMessage string
|
crewMessage string
|
||||||
crewAccount string
|
crewAccount string
|
||||||
crewAll bool
|
crewAgentOverride string
|
||||||
crewDryRun bool
|
crewAll bool
|
||||||
|
crewDryRun bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var crewCmd = &cobra.Command{
|
var crewCmd = &cobra.Command{
|
||||||
@@ -328,6 +329,7 @@ func init() {
|
|||||||
crewAtCmd.Flags().BoolVar(&crewNoTmux, "no-tmux", false, "Just print directory path")
|
crewAtCmd.Flags().BoolVar(&crewNoTmux, "no-tmux", false, "Just print directory path")
|
||||||
crewAtCmd.Flags().BoolVarP(&crewDetached, "detached", "d", false, "Start session without attaching")
|
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(&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().StringVar(&crewRig, "rig", "", "Rig to use")
|
||||||
crewRemoveCmd.Flags().BoolVar(&crewForce, "force", false, "Force remove (skip safety checks)")
|
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().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(&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().StringVar(&crewRig, "rig", "", "Rig to use (filter when using --all)")
|
||||||
crewStopCmd.Flags().BoolVar(&crewAll, "all", false, "Stop all running crew sessions")
|
crewStopCmd.Flags().BoolVar(&crewAll, "all", false, "Stop all running crew sessions")
|
||||||
|
|||||||
+14
-5
@@ -150,8 +150,11 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
|||||||
// This gives cleaner lifecycle: Claude exits → session ends (no intermediate shell)
|
// This gives cleaner lifecycle: Claude exits → session ends (no intermediate shell)
|
||||||
// Pass "gt prime" as initial prompt so Claude loads context immediately
|
// Pass "gt prime" as initial prompt so Claude loads context immediately
|
||||||
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
|
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
|
||||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "gt prime")
|
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, "gt prime", crewAgentOverride)
|
||||||
if err := t.RespawnPane(paneID, claudeCmd); err != nil {
|
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)
|
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
|
// Use respawn-pane to replace shell with Claude directly
|
||||||
// Pass "gt prime" as initial prompt so Claude loads context immediately
|
// Pass "gt prime" as initial prompt so Claude loads context immediately
|
||||||
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
|
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
|
||||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "gt prime")
|
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, "gt prime", crewAgentOverride)
|
||||||
if err := t.RespawnPane(paneID, claudeCmd); err != nil {
|
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)
|
return fmt.Errorf("restarting claude: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +191,10 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
|||||||
if isInTmuxSession(sessionID) {
|
if isInTmuxSession(sessionID) {
|
||||||
// We're in the session at a shell prompt - just start the agent directly
|
// 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
|
// 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)
|
fmt.Printf("Starting %s in current session...\n", agentCfg.Command)
|
||||||
return execAgent(agentCfg, "gt prime")
|
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
|
// Set the start.go flags before calling runStartCrew
|
||||||
startCrewRig = rigName
|
startCrewRig = rigName
|
||||||
startCrewAccount = crewAccount
|
startCrewAccount = crewAccount
|
||||||
|
startCrewAgentOverride = crewAgentOverride
|
||||||
|
|
||||||
// Use rig/name format for runStartCrew
|
// Use rig/name format for runStartCrew
|
||||||
fullName := rigName + "/" + name
|
fullName := rigName + "/" + name
|
||||||
|
|||||||
+14
-6
@@ -89,6 +89,8 @@ Stops the current session (if running) and starts a fresh one.`,
|
|||||||
RunE: runDeaconRestart,
|
RunE: runDeaconRestart,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var deaconAgentOverride string
|
||||||
|
|
||||||
var deaconHeartbeatCmd = &cobra.Command{
|
var deaconHeartbeatCmd = &cobra.Command{
|
||||||
Use: "heartbeat [action]",
|
Use: "heartbeat [action]",
|
||||||
Short: "Update the Deacon heartbeat",
|
Short: "Update the Deacon heartbeat",
|
||||||
@@ -203,7 +205,6 @@ Examples:
|
|||||||
RunE: runDeaconStaleHooks,
|
RunE: runDeaconStaleHooks,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
triggerTimeout time.Duration
|
triggerTimeout time.Duration
|
||||||
|
|
||||||
@@ -258,6 +259,10 @@ func init() {
|
|||||||
deaconStaleHooksCmd.Flags().BoolVar(&staleHooksDryRun, "dry-run", false,
|
deaconStaleHooksCmd.Flags().BoolVar(&staleHooksDryRun, "dry-run", false,
|
||||||
"Preview what would be unhooked without making changes")
|
"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)
|
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")
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +292,7 @@ func runDeaconStart(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startDeaconSession creates and initializes the Deacon tmux session.
|
// 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
|
// Find workspace root
|
||||||
townRoot, err := workspace.FindFromCwdOrError()
|
townRoot, err := workspace.FindFromCwdOrError()
|
||||||
if err != nil {
|
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
|
// Restarts are handled by daemon via ensureDeaconRunning on each heartbeat
|
||||||
// The startup hook handles context loading automatically
|
// The startup hook handles context loading automatically
|
||||||
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
|
// 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)
|
return fmt.Errorf("sending command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +403,7 @@ func runDeaconAttach(cmd *cobra.Command, args []string) error {
|
|||||||
if !running {
|
if !running {
|
||||||
// Auto-start if not running
|
// Auto-start if not running
|
||||||
fmt.Println("Deacon session not running, starting...")
|
fmt.Println("Deacon session not running, starting...")
|
||||||
if err := startDeaconSession(t, sessionName); err != nil {
|
if err := startDeaconSession(t, sessionName, deaconAgentOverride); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -942,4 +951,3 @@ func runDeaconStaleHooks(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-5
@@ -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.`,
|
tmux session. Use the subcommands to start, stop, attach, and check status.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mayorAgentOverride string
|
||||||
|
|
||||||
var mayorStartCmd = &cobra.Command{
|
var mayorStartCmd = &cobra.Command{
|
||||||
Use: "start",
|
Use: "start",
|
||||||
Short: "Start the Mayor session",
|
Short: "Start the Mayor session",
|
||||||
@@ -84,6 +86,10 @@ func init() {
|
|||||||
mayorCmd.AddCommand(mayorStatusCmd)
|
mayorCmd.AddCommand(mayorStatusCmd)
|
||||||
mayorCmd.AddCommand(mayorRestartCmd)
|
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)
|
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")
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +119,7 @@ func runMayorStart(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startMayorSession creates and initializes the Mayor tmux session.
|
// 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
|
// Find workspace root
|
||||||
townRoot, err := workspace.FindFromCwdOrError()
|
townRoot, err := workspace.FindFromCwdOrError()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -139,8 +145,11 @@ func startMayorSession(t *tmux.Tmux, sessionName string) error {
|
|||||||
// Use SendKeysDelayed to allow shell initialization after NewSession
|
// Use SendKeysDelayed to allow shell initialization after NewSession
|
||||||
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
|
// 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
|
// Mayor uses default runtime config (empty rigPath) since it's not rig-specific
|
||||||
claudeCmd := config.BuildAgentStartupCommand("mayor", "mayor", "", "")
|
startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", "", agentOverride)
|
||||||
if err := t.SendKeysDelayed(sessionName, claudeCmd, 200); err != nil {
|
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)
|
return fmt.Errorf("sending command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +217,7 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
|||||||
if !running {
|
if !running {
|
||||||
// Auto-start if not running
|
// Auto-start if not running
|
||||||
fmt.Println("Mayor session not running, starting...")
|
fmt.Println("Mayor session not running, starting...")
|
||||||
if err := startMayorSession(t, sessionName); err != nil {
|
if err := startMayorSession(t, sessionName, mayorAgentOverride); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-18
@@ -24,16 +24,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
startAll bool
|
startAll bool
|
||||||
startCrewRig string
|
startAgentOverride string
|
||||||
startCrewAccount string
|
startCrewRig string
|
||||||
shutdownGraceful bool
|
startCrewAccount string
|
||||||
shutdownWait int
|
startCrewAgentOverride string
|
||||||
shutdownAll bool
|
shutdownGraceful bool
|
||||||
shutdownForce bool
|
shutdownWait int
|
||||||
shutdownYes bool
|
shutdownAll bool
|
||||||
shutdownPolecatsOnly bool
|
shutdownForce bool
|
||||||
shutdownNuclear bool
|
shutdownYes bool
|
||||||
|
shutdownPolecatsOnly bool
|
||||||
|
shutdownNuclear bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var startCmd = &cobra.Command{
|
var startCmd = &cobra.Command{
|
||||||
@@ -104,9 +106,11 @@ Examples:
|
|||||||
func init() {
|
func init() {
|
||||||
startCmd.Flags().BoolVarP(&startAll, "all", "a", false,
|
startCmd.Flags().BoolVarP(&startAll, "all", "a", false,
|
||||||
"Also start Witnesses and Refineries for all rigs")
|
"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(&startCrewRig, "rig", "", "Rig to use")
|
||||||
startCrewCmd.Flags().StringVar(&startCrewAccount, "account", "", "Claude Code account handle 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)
|
startCmd.AddCommand(startCrewCmd)
|
||||||
|
|
||||||
shutdownCmd.Flags().BoolVarP(&shutdownGraceful, "graceful", "g", false,
|
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))
|
fmt.Printf("Starting Gas Town from %s\n\n", style.Dim.Render(townRoot))
|
||||||
|
|
||||||
// Start core agents (Mayor and Deacon)
|
// Start core agents (Mayor and Deacon)
|
||||||
if err := startCoreAgents(t); err != nil {
|
if err := startCoreAgents(t, startAgentOverride); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ func runStart(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startCoreAgents starts Mayor and Deacon sessions.
|
// startCoreAgents starts Mayor and Deacon sessions.
|
||||||
func startCoreAgents(t *tmux.Tmux) error {
|
func startCoreAgents(t *tmux.Tmux, agentOverride string) error {
|
||||||
// Get session names
|
// Get session names
|
||||||
mayorSession := getMayorSessionName()
|
mayorSession := getMayorSessionName()
|
||||||
deaconSession := getDeaconSessionName()
|
deaconSession := getDeaconSessionName()
|
||||||
@@ -193,7 +197,7 @@ func startCoreAgents(t *tmux.Tmux) error {
|
|||||||
fmt.Printf(" %s Mayor already running\n", style.Dim.Render("○"))
|
fmt.Printf(" %s Mayor already running\n", style.Dim.Render("○"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s Starting Mayor...\n", style.Bold.Render("→"))
|
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)
|
return fmt.Errorf("starting Mayor: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s Mayor started\n", style.Bold.Render("✓"))
|
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("○"))
|
fmt.Printf(" %s Deacon already running\n", style.Dim.Render("○"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s Starting Deacon...\n", style.Bold.Render("→"))
|
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)
|
return fmt.Errorf("starting Deacon: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s Deacon started\n", style.Bold.Render("✓"))
|
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) {
|
if !t.IsClaudeRunning(sessionID) {
|
||||||
// Claude has exited, restart it with "gt prime" as initial prompt
|
// Claude has exited, restart it with "gt prime" as initial prompt
|
||||||
fmt.Printf("Session exists, restarting Claude...\n")
|
fmt.Printf("Session exists, restarting Claude...\n")
|
||||||
claudeCmd := config.BuildCrewStartupCommand(rigName, name, r.Path, "gt prime")
|
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
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)
|
return fmt.Errorf("restarting claude: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -833,8 +840,11 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// Start claude with skip permissions and proper env vars for seance
|
// Start claude with skip permissions and proper env vars for seance
|
||||||
// Pass "gt prime" as initial prompt so context is loaded immediately
|
// Pass "gt prime" as initial prompt so context is loaded immediately
|
||||||
claudeCmd := config.BuildCrewStartupCommand(rigName, name, r.Path, "gt prime")
|
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
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)
|
return fmt.Errorf("starting claude: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1035,6 +1035,89 @@ func TestBuildPolecatStartupCommandWithAgentOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildAgentStartupCommandWithAgentOverride(t *testing.T) {
|
||||||
|
townRoot := t.TempDir()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Join(townRoot, "mayor"), 0755); err != nil {
|
||||||
|
t.Fatalf("MkdirAll: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(townRoot, "mayor", "town.json"), []byte("{}"), 0600); err != nil {
|
||||||
|
t.Fatalf("WriteFile town.json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
townSettings := NewTownSettings()
|
||||||
|
townSettings.DefaultAgent = "gemini"
|
||||||
|
if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil {
|
||||||
|
t.Fatalf("SaveTownSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
originalWd, _ := os.Getwd()
|
||||||
|
t.Cleanup(func() { _ = os.Chdir(originalWd) })
|
||||||
|
if err := os.Chdir(townRoot); err != nil {
|
||||||
|
t.Fatalf("Chdir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("empty override uses default agent", func(t *testing.T) {
|
||||||
|
cmd, err := BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildAgentStartupCommandWithAgentOverride: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "GT_ROLE=mayor") {
|
||||||
|
t.Fatalf("expected GT_ROLE export in command: %q", cmd)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "BD_ACTOR=mayor") {
|
||||||
|
t.Fatalf("expected BD_ACTOR export in command: %q", cmd)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "gemini --approval-mode yolo") {
|
||||||
|
t.Fatalf("expected gemini command in output: %q", cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("override switches agent", func(t *testing.T) {
|
||||||
|
cmd, err := BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", "", "codex")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildAgentStartupCommandWithAgentOverride: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "codex") {
|
||||||
|
t.Fatalf("expected codex command in output: %q", cmd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildCrewStartupCommandWithAgentOverride(t *testing.T) {
|
||||||
|
townRoot := t.TempDir()
|
||||||
|
rigPath := filepath.Join(townRoot, "testrig")
|
||||||
|
|
||||||
|
townSettings := NewTownSettings()
|
||||||
|
if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil {
|
||||||
|
t.Fatalf("SaveTownSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := SaveRigSettings(RigSettingsPath(rigPath), NewRigSettings()); err != nil {
|
||||||
|
t.Fatalf("SaveRigSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := BuildCrewStartupCommandWithAgentOverride("testrig", "max", rigPath, "gt prime", "gemini")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildCrewStartupCommandWithAgentOverride: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "GT_ROLE=crew") {
|
||||||
|
t.Fatalf("expected GT_ROLE export in command: %q", cmd)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "GT_RIG=testrig") {
|
||||||
|
t.Fatalf("expected GT_RIG export in command: %q", cmd)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "GT_CREW=max") {
|
||||||
|
t.Fatalf("expected GT_CREW export in command: %q", cmd)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "BD_ACTOR=testrig/crew/max") {
|
||||||
|
t.Fatalf("expected BD_ACTOR export in command: %q", cmd)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "gemini --approval-mode yolo") {
|
||||||
|
t.Fatalf("expected gemini command in output: %q", cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadRuntimeConfigFromSettings(t *testing.T) {
|
func TestLoadRuntimeConfigFromSettings(t *testing.T) {
|
||||||
// Create temp rig with custom runtime config
|
// Create temp rig with custom runtime config
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|||||||
Reference in New Issue
Block a user