fix: Honor town default_agent for town-level agents (mayor, deacon)

Previously, BuildStartupCommand, GetRuntimeCommand, and
GetRuntimeCommandWithPrompt would fall back to DefaultRuntimeConfig()
(hardcoded "claude") when rigPath was empty, instead of reading
the town settings for the default_agent.

This meant that `gt config default-agent` had no effect on town-level
agents like the mayor.

Fix: Added findTownRootFromCwd() to detect town root from cwd,
then call ResolveAgentConfig() to read the town's default_agent
setting and custom agents.

Now `gt mayor attach` (and other town-level agents) correctly use
the agent configured in town settings.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Darko Luketic
2026-01-05 07:40:49 +01:00
parent b6dd6f005a
commit c1897f5843

View File

@@ -808,7 +808,12 @@ func fillRuntimeDefaults(rc *RuntimeConfig) *RuntimeConfig {
// for starting an LLM session. It resolves the agent config and builds the command.
func GetRuntimeCommand(rigPath string) string {
if rigPath == "" {
return DefaultRuntimeConfig().BuildCommand()
// Try to detect town root from cwd for town-level agents (mayor, deacon)
townRoot, err := findTownRootFromCwd()
if err != nil {
return DefaultRuntimeConfig().BuildCommand()
}
return ResolveAgentConfig(townRoot, "").BuildCommand()
}
// Derive town root from rig path (rig is typically ~/gt/<rigname>)
townRoot := filepath.Dir(rigPath)
@@ -818,15 +823,50 @@ func GetRuntimeCommand(rigPath string) string {
// GetRuntimeCommandWithPrompt returns the full command with an initial prompt.
func GetRuntimeCommandWithPrompt(rigPath, prompt string) string {
if rigPath == "" {
return DefaultRuntimeConfig().BuildCommandWithPrompt(prompt)
// Try to detect town root from cwd for town-level agents (mayor, deacon)
townRoot, err := findTownRootFromCwd()
if err != nil {
return DefaultRuntimeConfig().BuildCommandWithPrompt(prompt)
}
return ResolveAgentConfig(townRoot, "").BuildCommandWithPrompt(prompt)
}
townRoot := filepath.Dir(rigPath)
return ResolveAgentConfig(townRoot, rigPath).BuildCommandWithPrompt(prompt)
}
// findTownRootFromCwd locates the town root by walking up from cwd.
// It looks for the mayor/town.json marker file.
// Returns empty string and no error if not found (caller should use defaults).
func findTownRootFromCwd() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("getting cwd: %w", err)
}
absDir, err := filepath.Abs(cwd)
if err != nil {
return "", fmt.Errorf("resolving path: %w", err)
}
const marker = "mayor/town.json"
current := absDir
for {
if _, err := os.Stat(filepath.Join(current, marker)); err == nil {
return current, nil
}
parent := filepath.Dir(current)
if parent == current {
return "", fmt.Errorf("town root not found (no %s marker)", marker)
}
current = parent
}
}
// BuildStartupCommand builds a full startup command with environment exports.
// envVars is a map of environment variable names to values.
// rigPath is optional - if empty, uses defaults.
// rigPath is optional - if empty, tries to detect town root from cwd.
// prompt is optional - if provided, appended as the initial prompt.
func BuildStartupCommand(envVars map[string]string, rigPath, prompt string) string {
var rc *RuntimeConfig
@@ -835,7 +875,13 @@ func BuildStartupCommand(envVars map[string]string, rigPath, prompt string) stri
townRoot := filepath.Dir(rigPath)
rc = ResolveAgentConfig(townRoot, rigPath)
} else {
rc = DefaultRuntimeConfig()
// Try to detect town root from cwd for town-level agents (mayor, deacon)
townRoot, err := findTownRootFromCwd()
if err != nil {
rc = DefaultRuntimeConfig()
} else {
rc = ResolveAgentConfig(townRoot, "")
}
}
// Build environment export prefix