Refactor startup paths to use RuntimeConfig (gt-j0546)

Replaced all hardcoded 'claude --dangerously-skip-permissions' invocations
with configurable helpers from internal/config:

- GetRuntimeCommand(rigPath) - simple command string
- GetRuntimeCommandWithPrompt(rigPath, prompt) - with initial prompt
- BuildAgentStartupCommand(role, bdActor, rigPath, prompt) - generic agent
- BuildPolecatStartupCommand(rigName, polecatName, rigPath, prompt) - polecat
- BuildCrewStartupCommand(rigName, crewName, rigPath, prompt) - crew
- BuildStartupCommand(envVars, rigPath, prompt) - custom env vars

Files updated:
- internal/cmd/start.go (4 locations)
- internal/cmd/crew_lifecycle.go (2 locations)
- internal/cmd/crew_at.go (2 locations)
- internal/cmd/deacon.go
- internal/cmd/witness.go
- internal/cmd/up.go (2 locations)
- internal/cmd/handoff.go (2 locations)
- internal/daemon/daemon.go (3 locations)
- internal/daemon/lifecycle.go
- internal/session/manager.go
- internal/refinery/manager.go
- internal/boot/boot.go

This enables future support for alternative LLM runtimes (aider, etc.)
via rig/town settings configuration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/gus
2025-12-30 23:48:16 -08:00
committed by Steve Yegge
parent 0c75088727
commit 626a24e013
12 changed files with 42 additions and 36 deletions

View File

@@ -150,8 +150,7 @@ 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
bdActor := fmt.Sprintf("%s/crew/%s", r.Name, name)
claudeCmd := fmt.Sprintf(`export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && claude --dangerously-skip-permissions "gt prime"`, r.Name, name, bdActor, bdActor)
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "gt prime")
if err := t.RespawnPane(paneID, claudeCmd); err != nil {
return fmt.Errorf("starting claude: %w", err)
}
@@ -175,8 +174,7 @@ 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
bdActor := fmt.Sprintf("%s/crew/%s", r.Name, name)
claudeCmd := fmt.Sprintf(`export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && claude --dangerously-skip-permissions "gt prime"`, r.Name, name, bdActor, bdActor)
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "gt prime")
if err := t.RespawnPane(paneID, claudeCmd); err != nil {
return fmt.Errorf("restarting claude: %w", err)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/crew"
"github.com/steveyegge/gastown/internal/mail"
@@ -335,8 +336,7 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
// Start claude with skip permissions (crew workers are trusted)
// Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes
bdActor := fmt.Sprintf("%s/crew/%s", r.Name, name)
claudeCmd := fmt.Sprintf("export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && claude --dangerously-skip-permissions", r.Name, name, bdActor, bdActor)
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, "")
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
fmt.Printf("Error starting claude for %s: %v\n", arg, err)
lastErr = err
@@ -511,8 +511,7 @@ func restartCrewSession(rigName, crewName, clonePath string) error {
}
// Start claude with skip permissions
bdActor := fmt.Sprintf("%s/crew/%s", rigName, crewName)
claudeCmd := fmt.Sprintf("export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && claude --dangerously-skip-permissions", rigName, crewName, bdActor, bdActor)
claudeCmd := config.BuildCrewStartupCommand(rigName, crewName, "", "")
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
return fmt.Errorf("starting claude: %w", err)
}

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/deacon"
"github.com/steveyegge/gastown/internal/polecat"
@@ -291,7 +292,7 @@ func startDeaconSession(t *tmux.Tmux) 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(DeaconSessionName, "export GT_ROLE=deacon BD_ACTOR=deacon GIT_AUTHOR_NAME=deacon && claude --dangerously-skip-permissions"); err != nil {
if err := t.SendKeys(DeaconSessionName, config.BuildAgentStartupCommand("deacon", "deacon", "", "")); err != nil {
return fmt.Errorf("sending command: %w", err)
}

View File

@@ -8,6 +8,7 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/session"
"github.com/steveyegge/gastown/internal/style"
@@ -327,10 +328,11 @@ func buildRestartCommand(sessionName string) (string, error) {
// IMPORTANT: Passing "gt prime" as argument injects it as the first prompt,
// which triggers the agent to execute immediately. Without this, agents
// wait for user input despite all GUPP prompting in hooks.
runtimeCmd := config.GetRuntimeCommandWithPrompt("", "gt prime")
if gtRole != "" {
return fmt.Sprintf("cd %s && export GT_ROLE=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && exec claude --dangerously-skip-permissions \"gt prime\"", workDir, gtRole, gtRole, gtRole), nil
return fmt.Sprintf("cd %s && export GT_ROLE=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && exec %s", workDir, gtRole, gtRole, gtRole, runtimeCmd), nil
}
return fmt.Sprintf("cd %s && exec claude --dangerously-skip-permissions \"gt prime\"", workDir), nil
return fmt.Sprintf("cd %s && exec %s", workDir, runtimeCmd), nil
}
// sessionWorkDir returns the correct working directory for a session.

View File

@@ -341,7 +341,8 @@ func ensureRefinerySession(rigName string, r *rig.Rig) (bool, error) {
// Launch Claude in a respawn loop
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
loopCmd := `export GT_ROLE=refinery BD_ACTOR=` + bdActor + ` GIT_AUTHOR_NAME=` + bdActor + ` && while true; do echo "🛢️ Starting Refinery for ` + rigName + `..."; claude --dangerously-skip-permissions; echo ""; echo "Refinery exited. Restarting in 2s... (Ctrl-C to stop)"; sleep 2; done`
runtimeCmd := config.GetRuntimeCommand(r.Path)
loopCmd := `export GT_ROLE=refinery BD_ACTOR=` + bdActor + ` GIT_AUTHOR_NAME=` + bdActor + ` && while true; do echo "🛢️ Starting Refinery for ` + rigName + `..."; ` + runtimeCmd + `; echo ""; echo "Refinery exited. Restarting in 2s... (Ctrl-C to stop)"; sleep 2; done`
if err := t.SendKeysDelayed(sessionName, loopCmd, 200); err != nil {
return false, fmt.Errorf("sending command: %w", err)
}
@@ -744,7 +745,7 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
if !t.IsClaudeRunning(sessionID) {
// Claude has exited, restart it
fmt.Printf("Session exists, restarting Claude...\n")
if err := t.SendKeys(sessionID, "claude --dangerously-skip-permissions"); err != nil {
if err := t.SendKeys(sessionID, config.GetRuntimeCommand(r.Path)); err != nil {
return fmt.Errorf("restarting claude: %w", err)
}
// Wait for Claude to start, then prime
@@ -785,7 +786,7 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
}
// Start claude with skip permissions
if err := t.SendKeys(sessionID, "claude --dangerously-skip-permissions"); err != nil {
if err := t.SendKeys(sessionID, config.GetRuntimeCommand(r.Path)); err != nil {
return fmt.Errorf("starting claude: %w", err)
}
@@ -926,7 +927,7 @@ func startCrewMember(rigName, crewName, townRoot string) error {
}
// Start claude
if err := t.SendKeys(sessionID, "claude --dangerously-skip-permissions"); err != nil {
if err := t.SendKeys(sessionID, config.GetRuntimeCommand(r.Path)); err != nil {
return fmt.Errorf("starting claude: %w", err)
}

View File

@@ -262,11 +262,12 @@ func ensureSession(t *tmux.Tmux, sessionName, workDir, role string) error {
// Launch Claude
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
var claudeCmd string
runtimeCmd := config.GetRuntimeCommand("")
if role == "deacon" {
// Deacon uses respawn loop
claudeCmd = `export GT_ROLE=deacon BD_ACTOR=deacon GIT_AUTHOR_NAME=deacon && while true; do echo "⛪ Starting Deacon session..."; claude --dangerously-skip-permissions; echo ""; echo "Deacon exited. Restarting in 2s... (Ctrl-C to stop)"; sleep 2; done`
claudeCmd = `export GT_ROLE=deacon BD_ACTOR=deacon GIT_AUTHOR_NAME=deacon && while true; do echo "⛪ Starting Deacon session..."; ` + runtimeCmd + `; echo ""; echo "Deacon exited. Restarting in 2s... (Ctrl-C to stop)"; sleep 2; done`
} else {
claudeCmd = fmt.Sprintf(`export GT_ROLE=%s BD_ACTOR=%s GIT_AUTHOR_NAME=%s && claude --dangerously-skip-permissions`, role, role, role)
claudeCmd = config.BuildAgentStartupCommand(role, role, "", "")
}
if err := t.SendKeysDelayed(sessionName, claudeCmd, 200); err != nil {

View File

@@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/claude"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/session"
@@ -333,7 +334,7 @@ func ensureWitnessSession(rigName string, r *rig.Rig) (bool, error) {
// Restarts are handled by daemon via LIFECYCLE mail or deacon health-scan
// NOTE: No gt prime injection needed - SessionStart hook handles it automatically
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
if err := t.SendKeys(sessionName, fmt.Sprintf("export GT_ROLE=witness BD_ACTOR=%s GIT_AUTHOR_NAME=%s && claude --dangerously-skip-permissions", bdActor, bdActor)); err != nil {
if err := t.SendKeys(sessionName, config.BuildAgentStartupCommand("witness", bdActor, "", "")); err != nil {
return false, fmt.Errorf("sending command: %w", err)
}