fix: honor rig agent when starting witness/refinery

This commit is contained in:
jv
2026-01-07 12:47:44 +13:00
committed by Steve Yegge
parent 6afd85df4b
commit 02ca9e43fa
4 changed files with 66 additions and 16 deletions

View File

@@ -371,7 +371,7 @@ func ensureRefinerySession(rigName string, r *rig.Rig) (bool, error) {
// Launch Claude directly (no respawn loop - daemon handles restart)
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
if err := t.SendKeys(sessionName, config.BuildAgentStartupCommand("refinery", bdActor, "", "")); err != nil {
if err := t.SendKeys(sessionName, config.BuildAgentStartupCommand("refinery", bdActor, r.Path, "")); err != nil {
return false, fmt.Errorf("sending command: %w", err)
}

View File

@@ -1118,6 +1118,53 @@ func TestBuildCrewStartupCommandWithAgentOverride(t *testing.T) {
}
}
func TestBuildStartupCommand_UsesRigAgentWhenRigPathProvided(t *testing.T) {
townRoot := t.TempDir()
rigPath := filepath.Join(townRoot, "testrig")
townSettings := NewTownSettings()
townSettings.DefaultAgent = "gemini"
if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil {
t.Fatalf("SaveTownSettings: %v", err)
}
rigSettings := NewRigSettings()
rigSettings.Agent = "codex"
if err := SaveRigSettings(RigSettingsPath(rigPath), rigSettings); err != nil {
t.Fatalf("SaveRigSettings: %v", err)
}
cmd := BuildStartupCommand(map[string]string{"GT_ROLE": "witness"}, rigPath, "")
if !strings.Contains(cmd, "codex") {
t.Fatalf("expected rig agent (codex) in command: %q", cmd)
}
if strings.Contains(cmd, "gemini --approval-mode yolo") {
t.Fatalf("did not expect town default agent in command: %q", cmd)
}
}
func TestGetRuntimeCommand_UsesRigAgentWhenRigPathProvided(t *testing.T) {
townRoot := t.TempDir()
rigPath := filepath.Join(townRoot, "testrig")
townSettings := NewTownSettings()
townSettings.DefaultAgent = "gemini"
if err := SaveTownSettings(TownSettingsPath(townRoot), townSettings); err != nil {
t.Fatalf("SaveTownSettings: %v", err)
}
rigSettings := NewRigSettings()
rigSettings.Agent = "codex"
if err := SaveRigSettings(RigSettingsPath(rigPath), rigSettings); err != nil {
t.Fatalf("SaveRigSettings: %v", err)
}
cmd := GetRuntimeCommand(rigPath)
if !strings.HasPrefix(cmd, "codex") {
t.Fatalf("GetRuntimeCommand() = %q, want prefix %q", cmd, "codex")
}
}
func TestLoadRuntimeConfigFromSettings(t *testing.T) {
// Create temp rig with custom runtime config
dir := t.TempDir()

View File

@@ -211,8 +211,8 @@ func (d *Daemon) executeLifecycleAction(request *LifecycleRequest) error {
// ParsedIdentity holds the components extracted from an agent identity string.
// This is used to look up the appropriate role bead for lifecycle config.
type ParsedIdentity struct {
RoleType string // mayor, deacon, witness, refinery, crew, polecat
RigName string // Empty for town-level agents (mayor, deacon)
RoleType string // mayor, deacon, witness, refinery, crew, polecat
RigName string // Empty for town-level agents (mayor, deacon)
AgentName string // Empty for singletons (mayor, deacon, witness, refinery)
}
@@ -436,12 +436,17 @@ func (d *Daemon) getStartCommand(roleConfig *beads.RoleConfig, parsed *ParsedIde
return beads.ExpandRolePattern(roleConfig.StartCommand, d.config.TownRoot, parsed.RigName, parsed.AgentName, parsed.RoleType)
}
rigPath := ""
if parsed != nil && parsed.RigName != "" {
rigPath = filepath.Join(d.config.TownRoot, parsed.RigName)
}
// Default command for all agents - use runtime config
defaultCmd := "exec " + config.GetRuntimeCommand("")
defaultCmd := "exec " + config.GetRuntimeCommand(rigPath)
// Polecats need environment variables set in the command
if parsed.RoleType == "polecat" {
return config.BuildPolecatStartupCommand(parsed.RigName, parsed.AgentName, "", "")
return config.BuildPolecatStartupCommand(parsed.RigName, parsed.AgentName, rigPath, "")
}
return defaultCmd
@@ -572,7 +577,7 @@ func (d *Daemon) getAgentBeadInfo(agentBeadID string) (*AgentBeadInfo, error) {
Type string `json:"issue_type"`
Description string `json:"description"`
UpdatedAt string `json:"updated_at"`
HookBead string `json:"hook_bead"` // Read from database column
HookBead string `json:"hook_bead"` // Read from database column
AgentState string `json:"agent_state"` // Read from database column
}
@@ -913,7 +918,7 @@ func (d *Daemon) getDeadAgents() []deadAgentInfo {
var agents []struct {
ID string `json:"id"`
Type string `json:"issue_type"`
HookBead string `json:"hook_bead"` // Read from database column
HookBead string `json:"hook_bead"` // Read from database column
AgentState string `json:"agent_state"` // Read from database column
}
@@ -972,4 +977,3 @@ Action needed: Either restart the agent or reassign the work.`,
d.logger.Printf("Notified %s of orphaned work for %s", witnessAddr, agentID)
}
}

View File

@@ -26,9 +26,9 @@ import (
// Common errors
var (
ErrNotRunning = errors.New("refinery not running")
ErrNotRunning = errors.New("refinery not running")
ErrAlreadyRunning = errors.New("refinery already running")
ErrNoQueue = errors.New("no items in queue")
ErrNoQueue = errors.New("no items in queue")
)
// Manager handles refinery lifecycle and queue operations.
@@ -205,7 +205,7 @@ func (m *Manager) Start(foreground bool) error {
// NOTE: No gt prime injection needed - SessionStart hook handles it automatically
// Restarts are handled by daemon via LIFECYCLE mail, not shell loops
// Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes
command := config.BuildAgentStartupCommand("refinery", bdActor, "", "")
command := config.BuildAgentStartupCommand("refinery", bdActor, m.rig.Path, "")
if err := t.SendKeys(sessionID, command); err != nil {
// Clean up the session on failure (best-effort cleanup)
_ = t.KillSession(sessionID)
@@ -566,7 +566,6 @@ func (m *Manager) pushWithRetry(targetBranch string, config MergeConfig) error {
return fmt.Errorf("push failed after %d retries: %v", config.PushRetryCount, lastErr)
}
// formatAge formats a duration since the given time.
func formatAge(t time.Time) string {
d := time.Since(t)
@@ -587,8 +586,8 @@ func formatAge(t time.Time) string {
func (m *Manager) notifyWorkerConflict(mr *MergeRequest) {
router := mail.NewRouter(m.workDir)
msg := &mail.Message{
From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
Subject: "Merge conflict - rebase required",
Body: fmt.Sprintf(`Your branch %s has conflicts with %s.
@@ -608,8 +607,8 @@ Then the Refinery will retry the merge.`,
func (m *Manager) notifyWorkerMerged(mr *MergeRequest) {
router := mail.NewRouter(m.workDir)
msg := &mail.Message{
From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
Subject: "Work merged successfully",
Body: fmt.Sprintf(`Your branch %s has been merged to %s.