feat: add --agent override for sling
This commit is contained in:
@@ -39,6 +39,7 @@ type SlingSpawnOptions struct {
|
|||||||
Account string // Claude Code account handle to use
|
Account string // Claude Code account handle to use
|
||||||
Create bool // Create polecat if it doesn't exist (currently always true for sling)
|
Create bool // Create polecat if it doesn't exist (currently always true for sling)
|
||||||
HookBead string // Bead ID to set as hook_bead at spawn time (atomic assignment)
|
HookBead string // Bead ID to set as hook_bead at spawn time (atomic assignment)
|
||||||
|
Agent string // Agent override for this spawn (e.g., "gemini", "codex", "claude-haiku")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpawnPolecatForSling creates a fresh polecat and optionally starts its session.
|
// SpawnPolecatForSling creates a fresh polecat and optionally starts its session.
|
||||||
@@ -122,8 +123,11 @@ func SpawnPolecatForSling(rigName string, opts SlingSpawnOptions) (*SpawnedPolec
|
|||||||
fmt.Printf("Polecat created. Agent must be started manually.\n\n")
|
fmt.Printf("Polecat created. Agent must be started manually.\n\n")
|
||||||
fmt.Printf("To start the agent:\n")
|
fmt.Printf("To start the agent:\n")
|
||||||
fmt.Printf(" cd %s\n", polecatObj.ClonePath)
|
fmt.Printf(" cd %s\n", polecatObj.ClonePath)
|
||||||
// Use rig's configured agent command
|
// Use rig's configured agent command, unless overridden.
|
||||||
agentCmd := config.ResolveAgentConfig(townRoot, r.Path).BuildCommand()
|
agentCmd, err := config.GetRuntimeCommandWithAgentOverride(r.Path, opts.Agent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
fmt.Printf(" %s\n\n", agentCmd)
|
fmt.Printf(" %s\n\n", agentCmd)
|
||||||
fmt.Printf("Agent will discover work via gt prime on startup.\n")
|
fmt.Printf("Agent will discover work via gt prime on startup.\n")
|
||||||
|
|
||||||
@@ -157,6 +161,13 @@ func SpawnPolecatForSling(rigName string, opts SlingSpawnOptions) (*SpawnedPolec
|
|||||||
startOpts := session.StartOptions{
|
startOpts := session.StartOptions{
|
||||||
ClaudeConfigDir: claudeConfigDir,
|
ClaudeConfigDir: claudeConfigDir,
|
||||||
}
|
}
|
||||||
|
if opts.Agent != "" {
|
||||||
|
cmd, err := config.BuildPolecatStartupCommandWithAgentOverride(rigName, polecatName, r.Path, "", opts.Agent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
startOpts.Command = cmd
|
||||||
|
}
|
||||||
if err := sessMgr.Start(polecatName, startOpts); err != nil {
|
if err := sessMgr.Start(polecatName, startOpts); err != nil {
|
||||||
return nil, fmt.Errorf("starting session: %w", err)
|
return nil, fmt.Errorf("starting session: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ var (
|
|||||||
slingCreate bool // --create: create polecat if it doesn't exist
|
slingCreate bool // --create: create polecat if it doesn't exist
|
||||||
slingForce bool // --force: force spawn even if polecat has unread mail
|
slingForce bool // --force: force spawn even if polecat has unread mail
|
||||||
slingAccount string // --account: Claude Code account handle to use
|
slingAccount string // --account: Claude Code account handle to use
|
||||||
|
slingAgent string // --agent: override runtime agent for this sling/spawn
|
||||||
slingNoConvoy bool // --no-convoy: skip auto-convoy creation
|
slingNoConvoy bool // --no-convoy: skip auto-convoy creation
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -120,6 +121,7 @@ func init() {
|
|||||||
slingCmd.Flags().BoolVar(&slingCreate, "create", false, "Create polecat if it doesn't exist")
|
slingCmd.Flags().BoolVar(&slingCreate, "create", false, "Create polecat if it doesn't exist")
|
||||||
slingCmd.Flags().BoolVar(&slingForce, "force", false, "Force spawn even if polecat has unread mail")
|
slingCmd.Flags().BoolVar(&slingForce, "force", false, "Force spawn even if polecat has unread mail")
|
||||||
slingCmd.Flags().StringVar(&slingAccount, "account", "", "Claude Code account handle to use")
|
slingCmd.Flags().StringVar(&slingAccount, "account", "", "Claude Code account handle to use")
|
||||||
|
slingCmd.Flags().StringVar(&slingAgent, "agent", "", "Override agent/runtime for this sling (e.g., claude, gemini, codex, or custom alias)")
|
||||||
slingCmd.Flags().BoolVar(&slingNoConvoy, "no-convoy", false, "Skip auto-convoy creation for single-issue sling")
|
slingCmd.Flags().BoolVar(&slingNoConvoy, "no-convoy", false, "Skip auto-convoy creation for single-issue sling")
|
||||||
|
|
||||||
rootCmd.AddCommand(slingCmd)
|
rootCmd.AddCommand(slingCmd)
|
||||||
@@ -243,6 +245,7 @@ func runSling(cmd *cobra.Command, args []string) error {
|
|||||||
Account: slingAccount,
|
Account: slingAccount,
|
||||||
Create: slingCreate,
|
Create: slingCreate,
|
||||||
HookBead: beadID, // Set atomically at spawn time
|
HookBead: beadID, // Set atomically at spawn time
|
||||||
|
Agent: slingAgent,
|
||||||
}
|
}
|
||||||
spawnInfo, spawnErr := SpawnPolecatForSling(rigName, spawnOpts)
|
spawnInfo, spawnErr := SpawnPolecatForSling(rigName, spawnOpts)
|
||||||
if spawnErr != nil {
|
if spawnErr != nil {
|
||||||
@@ -849,6 +852,7 @@ func runSlingFormula(args []string) error {
|
|||||||
Naked: slingNaked,
|
Naked: slingNaked,
|
||||||
Account: slingAccount,
|
Account: slingAccount,
|
||||||
Create: slingCreate,
|
Create: slingCreate,
|
||||||
|
Agent: slingAgent,
|
||||||
}
|
}
|
||||||
spawnInfo, spawnErr := SpawnPolecatForSling(rigName, spawnOpts)
|
spawnInfo, spawnErr := SpawnPolecatForSling(rigName, spawnOpts)
|
||||||
if spawnErr != nil {
|
if spawnErr != nil {
|
||||||
@@ -1101,7 +1105,6 @@ func agentIDToBeadID(agentID, townRoot string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// IsDogTarget checks if target is a dog target pattern.
|
// IsDogTarget checks if target is a dog target pattern.
|
||||||
// Returns the dog name (or empty for pool dispatch) and true if it's a dog target.
|
// Returns the dog name (or empty for pool dispatch) and true if it's a dog target.
|
||||||
// Patterns:
|
// Patterns:
|
||||||
@@ -1395,6 +1398,7 @@ func runBatchSling(beadIDs []string, rigName string, townBeadsDir string) error
|
|||||||
Account: slingAccount,
|
Account: slingAccount,
|
||||||
Create: slingCreate,
|
Create: slingCreate,
|
||||||
HookBead: beadID, // Set atomically at spawn time
|
HookBead: beadID, // Set atomically at spawn time
|
||||||
|
Agent: slingAgent,
|
||||||
}
|
}
|
||||||
spawnInfo, err := SpawnPolecatForSling(rigName, spawnOpts)
|
spawnInfo, err := SpawnPolecatForSling(rigName, spawnOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -838,6 +838,61 @@ func ResolveAgentConfig(townRoot, rigPath string) *RuntimeConfig {
|
|||||||
return lookupAgentConfig(agentName, townSettings)
|
return lookupAgentConfig(agentName, townSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveAgentConfigWithOverride resolves the agent configuration for a rig, with an optional override.
|
||||||
|
// If agentOverride is non-empty, it is used instead of rig/town defaults.
|
||||||
|
// Returns the resolved RuntimeConfig, the selected agent name, and an error if the override name
|
||||||
|
// does not exist in town custom agents or built-in presets.
|
||||||
|
func ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride string) (*RuntimeConfig, string, error) {
|
||||||
|
// Load rig settings
|
||||||
|
rigSettings, err := LoadRigSettings(RigSettingsPath(rigPath))
|
||||||
|
if err != nil {
|
||||||
|
rigSettings = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility: if Runtime is set directly, use it (but still report agentOverride if present)
|
||||||
|
if rigSettings != nil && rigSettings.Runtime != nil && agentOverride == "" {
|
||||||
|
rc := rigSettings.Runtime
|
||||||
|
return fillRuntimeDefaults(rc), "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load town settings for agent lookup
|
||||||
|
townSettings, err := LoadOrCreateTownSettings(TownSettingsPath(townRoot))
|
||||||
|
if err != nil {
|
||||||
|
townSettings = NewTownSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load custom agent registry if it exists
|
||||||
|
_ = LoadAgentRegistry(DefaultAgentRegistryPath(townRoot))
|
||||||
|
|
||||||
|
// Determine which agent name to use
|
||||||
|
agentName := ""
|
||||||
|
if agentOverride != "" {
|
||||||
|
agentName = agentOverride
|
||||||
|
} else if rigSettings != nil && rigSettings.Agent != "" {
|
||||||
|
agentName = rigSettings.Agent
|
||||||
|
} else if townSettings.DefaultAgent != "" {
|
||||||
|
agentName = townSettings.DefaultAgent
|
||||||
|
} else {
|
||||||
|
agentName = "claude" // ultimate fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an override is requested, validate it exists.
|
||||||
|
if agentOverride != "" {
|
||||||
|
if townSettings.Agents != nil {
|
||||||
|
if custom, ok := townSettings.Agents[agentName]; ok && custom != nil {
|
||||||
|
return fillRuntimeDefaults(custom), agentName, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if preset := GetAgentPresetByName(agentName); preset != nil {
|
||||||
|
return RuntimeConfigFromPreset(AgentPreset(agentName)), agentName, nil
|
||||||
|
}
|
||||||
|
return nil, "", fmt.Errorf("agent '%s' not found", agentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal lookup path (no override)
|
||||||
|
return lookupAgentConfig(agentName, townSettings), agentName, nil
|
||||||
|
}
|
||||||
|
|
||||||
// lookupAgentConfig looks up an agent by name.
|
// lookupAgentConfig looks up an agent by name.
|
||||||
// First checks town's custom agents, then built-in presets from agents.go.
|
// First checks town's custom agents, then built-in presets from agents.go.
|
||||||
func lookupAgentConfig(name string, townSettings *TownSettings) *RuntimeConfig {
|
func lookupAgentConfig(name string, townSettings *TownSettings) *RuntimeConfig {
|
||||||
@@ -893,6 +948,29 @@ func GetRuntimeCommand(rigPath string) string {
|
|||||||
return ResolveAgentConfig(townRoot, rigPath).BuildCommand()
|
return ResolveAgentConfig(townRoot, rigPath).BuildCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRuntimeCommandWithAgentOverride returns the full command for starting an LLM session,
|
||||||
|
// using agentOverride if non-empty.
|
||||||
|
func GetRuntimeCommandWithAgentOverride(rigPath, agentOverride string) (string, error) {
|
||||||
|
if rigPath == "" {
|
||||||
|
townRoot, err := findTownRootFromCwd()
|
||||||
|
if err != nil {
|
||||||
|
return DefaultRuntimeConfig().BuildCommand(), nil
|
||||||
|
}
|
||||||
|
rc, _, resolveErr := ResolveAgentConfigWithOverride(townRoot, "", agentOverride)
|
||||||
|
if resolveErr != nil {
|
||||||
|
return "", resolveErr
|
||||||
|
}
|
||||||
|
return rc.BuildCommand(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
townRoot := filepath.Dir(rigPath)
|
||||||
|
rc, _, err := ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return rc.BuildCommand(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetRuntimeCommandWithPrompt returns the full command with an initial prompt.
|
// GetRuntimeCommandWithPrompt returns the full command with an initial prompt.
|
||||||
func GetRuntimeCommandWithPrompt(rigPath, prompt string) string {
|
func GetRuntimeCommandWithPrompt(rigPath, prompt string) string {
|
||||||
if rigPath == "" {
|
if rigPath == "" {
|
||||||
@@ -907,6 +985,29 @@ func GetRuntimeCommandWithPrompt(rigPath, prompt string) string {
|
|||||||
return ResolveAgentConfig(townRoot, rigPath).BuildCommandWithPrompt(prompt)
|
return ResolveAgentConfig(townRoot, rigPath).BuildCommandWithPrompt(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRuntimeCommandWithPromptAndAgentOverride returns the full command with an initial prompt,
|
||||||
|
// using agentOverride if non-empty.
|
||||||
|
func GetRuntimeCommandWithPromptAndAgentOverride(rigPath, prompt, agentOverride string) (string, error) {
|
||||||
|
if rigPath == "" {
|
||||||
|
townRoot, err := findTownRootFromCwd()
|
||||||
|
if err != nil {
|
||||||
|
return DefaultRuntimeConfig().BuildCommandWithPrompt(prompt), nil
|
||||||
|
}
|
||||||
|
rc, _, resolveErr := ResolveAgentConfigWithOverride(townRoot, "", agentOverride)
|
||||||
|
if resolveErr != nil {
|
||||||
|
return "", resolveErr
|
||||||
|
}
|
||||||
|
return rc.BuildCommandWithPrompt(prompt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
townRoot := filepath.Dir(rigPath)
|
||||||
|
rc, _, err := ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return rc.BuildCommandWithPrompt(prompt), nil
|
||||||
|
}
|
||||||
|
|
||||||
// findTownRootFromCwd locates the town root by walking up from cwd.
|
// findTownRootFromCwd locates the town root by walking up from cwd.
|
||||||
// It looks for the mayor/town.json marker file.
|
// It looks for the mayor/town.json marker file.
|
||||||
// Returns empty string and no error if not found (caller should use defaults).
|
// Returns empty string and no error if not found (caller should use defaults).
|
||||||
@@ -981,6 +1082,52 @@ func BuildStartupCommand(envVars map[string]string, rigPath, prompt string) stri
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildStartupCommandWithAgentOverride builds a startup command like BuildStartupCommand,
|
||||||
|
// but uses agentOverride if non-empty.
|
||||||
|
func BuildStartupCommandWithAgentOverride(envVars map[string]string, rigPath, prompt, agentOverride string) (string, error) {
|
||||||
|
var rc *RuntimeConfig
|
||||||
|
|
||||||
|
if rigPath != "" {
|
||||||
|
townRoot := filepath.Dir(rigPath)
|
||||||
|
var err error
|
||||||
|
rc, _, err = ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
townRoot, err := findTownRootFromCwd()
|
||||||
|
if err != nil {
|
||||||
|
rc = DefaultRuntimeConfig()
|
||||||
|
} else {
|
||||||
|
var resolveErr error
|
||||||
|
rc, _, resolveErr = ResolveAgentConfigWithOverride(townRoot, "", agentOverride)
|
||||||
|
if resolveErr != nil {
|
||||||
|
return "", resolveErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build environment export prefix
|
||||||
|
var exports []string
|
||||||
|
for k, v := range envVars {
|
||||||
|
exports = append(exports, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(exports)
|
||||||
|
|
||||||
|
var cmd string
|
||||||
|
if len(exports) > 0 {
|
||||||
|
cmd = "export " + strings.Join(exports, " ") + " && "
|
||||||
|
}
|
||||||
|
|
||||||
|
if prompt != "" {
|
||||||
|
cmd += rc.BuildCommandWithPrompt(prompt)
|
||||||
|
} else {
|
||||||
|
cmd += rc.BuildCommand()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
// BuildAgentStartupCommand is a convenience function for starting agent sessions.
|
// BuildAgentStartupCommand is a convenience function for starting agent sessions.
|
||||||
// It sets standard environment variables (GT_ROLE, BD_ACTOR, GIT_AUTHOR_NAME)
|
// It sets standard environment variables (GT_ROLE, BD_ACTOR, GIT_AUTHOR_NAME)
|
||||||
// and builds the full startup command.
|
// and builds the full startup command.
|
||||||
@@ -993,6 +1140,16 @@ func BuildAgentStartupCommand(role, bdActor, rigPath, prompt string) string {
|
|||||||
return BuildStartupCommand(envVars, rigPath, prompt)
|
return BuildStartupCommand(envVars, rigPath, prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildAgentStartupCommandWithAgentOverride is like BuildAgentStartupCommand, but uses agentOverride if non-empty.
|
||||||
|
func BuildAgentStartupCommandWithAgentOverride(role, bdActor, rigPath, prompt, agentOverride string) (string, error) {
|
||||||
|
envVars := map[string]string{
|
||||||
|
"GT_ROLE": role,
|
||||||
|
"BD_ACTOR": bdActor,
|
||||||
|
"GIT_AUTHOR_NAME": bdActor,
|
||||||
|
}
|
||||||
|
return BuildStartupCommandWithAgentOverride(envVars, rigPath, prompt, agentOverride)
|
||||||
|
}
|
||||||
|
|
||||||
// BuildPolecatStartupCommand builds the startup command for a polecat.
|
// BuildPolecatStartupCommand builds the startup command for a polecat.
|
||||||
// Sets GT_ROLE, GT_RIG, GT_POLECAT, BD_ACTOR, and GIT_AUTHOR_NAME.
|
// Sets GT_ROLE, GT_RIG, GT_POLECAT, BD_ACTOR, and GIT_AUTHOR_NAME.
|
||||||
func BuildPolecatStartupCommand(rigName, polecatName, rigPath, prompt string) string {
|
func BuildPolecatStartupCommand(rigName, polecatName, rigPath, prompt string) string {
|
||||||
@@ -1007,6 +1164,19 @@ func BuildPolecatStartupCommand(rigName, polecatName, rigPath, prompt string) st
|
|||||||
return BuildStartupCommand(envVars, rigPath, prompt)
|
return BuildStartupCommand(envVars, rigPath, prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildPolecatStartupCommandWithAgentOverride is like BuildPolecatStartupCommand, but uses agentOverride if non-empty.
|
||||||
|
func BuildPolecatStartupCommandWithAgentOverride(rigName, polecatName, rigPath, prompt, agentOverride string) (string, error) {
|
||||||
|
bdActor := fmt.Sprintf("%s/polecats/%s", rigName, polecatName)
|
||||||
|
envVars := map[string]string{
|
||||||
|
"GT_ROLE": "polecat",
|
||||||
|
"GT_RIG": rigName,
|
||||||
|
"GT_POLECAT": polecatName,
|
||||||
|
"BD_ACTOR": bdActor,
|
||||||
|
"GIT_AUTHOR_NAME": polecatName,
|
||||||
|
}
|
||||||
|
return BuildStartupCommandWithAgentOverride(envVars, rigPath, prompt, agentOverride)
|
||||||
|
}
|
||||||
|
|
||||||
// BuildCrewStartupCommand builds the startup command for a crew member.
|
// BuildCrewStartupCommand builds the startup command for a crew member.
|
||||||
// Sets GT_ROLE, GT_RIG, GT_CREW, BD_ACTOR, and GIT_AUTHOR_NAME.
|
// Sets GT_ROLE, GT_RIG, GT_CREW, BD_ACTOR, and GIT_AUTHOR_NAME.
|
||||||
func BuildCrewStartupCommand(rigName, crewName, rigPath, prompt string) string {
|
func BuildCrewStartupCommand(rigName, crewName, rigPath, prompt string) string {
|
||||||
@@ -1021,6 +1191,19 @@ func BuildCrewStartupCommand(rigName, crewName, rigPath, prompt string) string {
|
|||||||
return BuildStartupCommand(envVars, rigPath, prompt)
|
return BuildStartupCommand(envVars, rigPath, prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildCrewStartupCommandWithAgentOverride is like BuildCrewStartupCommand, but uses agentOverride if non-empty.
|
||||||
|
func BuildCrewStartupCommandWithAgentOverride(rigName, crewName, rigPath, prompt, agentOverride string) (string, error) {
|
||||||
|
bdActor := fmt.Sprintf("%s/crew/%s", rigName, crewName)
|
||||||
|
envVars := map[string]string{
|
||||||
|
"GT_ROLE": "crew",
|
||||||
|
"GT_RIG": rigName,
|
||||||
|
"GT_CREW": crewName,
|
||||||
|
"BD_ACTOR": bdActor,
|
||||||
|
"GIT_AUTHOR_NAME": crewName,
|
||||||
|
}
|
||||||
|
return BuildStartupCommandWithAgentOverride(envVars, rigPath, prompt, agentOverride)
|
||||||
|
}
|
||||||
|
|
||||||
// GetRigPrefix returns the beads prefix for a rig from rigs.json.
|
// GetRigPrefix returns the beads prefix for a rig from rigs.json.
|
||||||
// Falls back to "gt" if the rig isn't found or has no prefix configured.
|
// Falls back to "gt" if the rig isn't found or has no prefix configured.
|
||||||
// townRoot is the path to the town directory (e.g., ~/gt).
|
// townRoot is the path to the town directory (e.g., ~/gt).
|
||||||
|
|||||||
@@ -931,6 +931,110 @@ func TestBuildCrewStartupCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveAgentConfigWithOverride(t *testing.T) {
|
||||||
|
townRoot := t.TempDir()
|
||||||
|
rigPath := filepath.Join(townRoot, "testrig")
|
||||||
|
|
||||||
|
// Town settings: default agent is gemini, plus a custom alias.
|
||||||
|
townSettings := NewTownSettings()
|
||||||
|
townSettings.DefaultAgent = "gemini"
|
||||||
|
townSettings.Agents["claude-haiku"] = &RuntimeConfig{
|
||||||
|
Command: "claude",
|
||||||
|
Args: []string{"--model", "haiku", "--dangerously-skip-permissions"},
|
||||||
|
}
|
||||||
|
if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil {
|
||||||
|
t.Fatalf("SaveTownSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rig settings: prefer codex unless overridden.
|
||||||
|
rigSettings := NewRigSettings()
|
||||||
|
rigSettings.Agent = "codex"
|
||||||
|
if err := SaveRigSettings(RigSettingsPath(rigPath), rigSettings); err != nil {
|
||||||
|
t.Fatalf("SaveRigSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("no override uses rig agent", func(t *testing.T) {
|
||||||
|
rc, name, err := ResolveAgentConfigWithOverride(townRoot, rigPath, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolveAgentConfigWithOverride: %v", err)
|
||||||
|
}
|
||||||
|
if name != "codex" {
|
||||||
|
t.Fatalf("name = %q, want %q", name, "codex")
|
||||||
|
}
|
||||||
|
if rc.Command != "codex" {
|
||||||
|
t.Fatalf("rc.Command = %q, want %q", rc.Command, "codex")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("override uses built-in preset", func(t *testing.T) {
|
||||||
|
rc, name, err := ResolveAgentConfigWithOverride(townRoot, rigPath, "gemini")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolveAgentConfigWithOverride: %v", err)
|
||||||
|
}
|
||||||
|
if name != "gemini" {
|
||||||
|
t.Fatalf("name = %q, want %q", name, "gemini")
|
||||||
|
}
|
||||||
|
if rc.Command != "gemini" {
|
||||||
|
t.Fatalf("rc.Command = %q, want %q", rc.Command, "gemini")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("override uses custom agent alias", func(t *testing.T) {
|
||||||
|
rc, name, err := ResolveAgentConfigWithOverride(townRoot, rigPath, "claude-haiku")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolveAgentConfigWithOverride: %v", err)
|
||||||
|
}
|
||||||
|
if name != "claude-haiku" {
|
||||||
|
t.Fatalf("name = %q, want %q", name, "claude-haiku")
|
||||||
|
}
|
||||||
|
if rc.Command != "claude" {
|
||||||
|
t.Fatalf("rc.Command = %q, want %q", rc.Command, "claude")
|
||||||
|
}
|
||||||
|
if got := rc.BuildCommand(); got != "claude --model haiku --dangerously-skip-permissions" {
|
||||||
|
t.Fatalf("BuildCommand() = %q, want %q", got, "claude --model haiku --dangerously-skip-permissions")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown override errors", func(t *testing.T) {
|
||||||
|
_, _, err := ResolveAgentConfigWithOverride(townRoot, rigPath, "nope-not-an-agent")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for unknown agent override")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPolecatStartupCommandWithAgentOverride(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rig settings file must exist for resolver calls that load it.
|
||||||
|
if err := SaveRigSettings(RigSettingsPath(rigPath), NewRigSettings()); err != nil {
|
||||||
|
t.Fatalf("SaveRigSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := BuildPolecatStartupCommandWithAgentOverride("testrig", "toast", rigPath, "", "gemini")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildPolecatStartupCommandWithAgentOverride: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(cmd, "GT_ROLE=polecat") {
|
||||||
|
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_POLECAT=toast") {
|
||||||
|
t.Fatalf("expected GT_POLECAT 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